<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Guillaume Laforge</title><link>https://glaforge.dev/</link><description>Recent content on Guillaume Laforge</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.</copyright><lastBuildDate>Thu, 16 Apr 2026 14:55:48 +0200</lastBuildDate><atom:link href="https://glaforge.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Streaming Gemini 3.1's expressive new TTS model in Java</title><link>https://glaforge.dev/posts/2026/04/16/streaming-gemini-3-1-expressive-new-tts-model-in-java/</link><pubDate>Thu, 16 Apr 2026 14:55:48 +0200</pubDate><guid>https://glaforge.dev/posts/2026/04/16/streaming-gemini-3-1-expressive-new-tts-model-in-java/</guid><description>&lt;p>Google just &lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-1-flash-tts/">released&lt;/a>
&lt;strong>Gemini 3.1 Flash Text-to-Speech (TTS)&lt;/strong>, a new expressive TTS model that you can steer with audio tags and scene descriptions.&lt;/p>
&lt;p>I wanted to see how it worked with the &lt;a href="https://github.com/glaforge/gemini-interactions-api-sdk">Gemini Interactions SDK for Java&lt;/a>.&lt;/p>
&lt;h2 id="expressive-control">Expressive control&lt;/h2>
&lt;p>The model sounds natural out of the box, but the real benefit is the control you have over expressiveness.
By defining &lt;em>&amp;ldquo;Audio Profiles&amp;rdquo;&lt;/em>, &lt;em>&amp;ldquo;Scene Details&amp;rdquo;&lt;/em>, and &lt;em>&amp;ldquo;Director&amp;rsquo;s Notes&amp;rdquo;&lt;/em> in your prompt,
you can control the character&amp;rsquo;s pacing, tone, and environment.&lt;/p></description><content:encoded>
<![CDATA[<p>Google just <a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-1-flash-tts/">released</a>
<strong>Gemini 3.1 Flash Text-to-Speech (TTS)</strong>, a new expressive TTS model that you can steer with audio tags and scene descriptions.</p>
<p>I wanted to see how it worked with the <a href="https://github.com/glaforge/gemini-interactions-api-sdk">Gemini Interactions SDK for Java</a>.</p>
<h2 id="expressive-control">Expressive control</h2>
<p>The model sounds natural out of the box, but the real benefit is the control you have over expressiveness.
By defining <em>&ldquo;Audio Profiles&rdquo;</em>, <em>&ldquo;Scene Details&rdquo;</em>, and <em>&ldquo;Director&rsquo;s Notes&rdquo;</em> in your prompt,
you can control the character&rsquo;s pacing, tone, and environment.</p>
<p>You can also use inline tags like <code>[excitedly]</code>, <code>[whispers]</code>, or <code>[shouting]</code>
to change the emotional delivery mid-sentence.
There&rsquo;s not a finite set of tags you can use, you can express any emotion within the square brackets.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>To learn more about prompting Gemini 3.1 TTS</span>
      </div>
      <div class="admonition-content">
        <p>For more on the prompting mechanics, see this article from DEV Community:</p>
<p><a href="https://dev.to/googleai/how-to-prompt-gemini-31s-new-text-to-speech-model-24bb">How to prompt Gemini 3.1&rsquo;s new text to speech model</a></p>
<p>The article even suggests a <em>meta-prompt</em> you can use to generate good prompts for Gemini 3.1 TTS!
You could even turn that into a reusable <code>SKILL.md</code> file!</p>
      </div>
    </div><h2 id="streaming-audio-directly-to-the-speakers">Streaming audio directly to the speakers</h2>
<p>I set up a <em>&ldquo;Morning DJ&rdquo;</em> persona using the example and techniques from the article.
Beyond just generating a file, I wanted to stream the audio directly to the speakers as the model generated it.</p>
<p>Here is the implementation using the Gemini Interactions Java SDK.</p>
<p>First, let&rsquo;s define the client, with an Gemini API key
(that you can get from <a href="https://aistudio.google.com/api-keys">AI Studio</a>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>GeminiInteractionsClient<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GeminiInteractionsClient.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>You don&rsquo;t necessarily have to create such a complex and detailed prompt,
but I&rsquo;ve reused the example from the article:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>prompt<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    # AUDIO PROFILE: Jaz R.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    ## THE SCENE: The London Studio
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    It is 10:00 PM in a glass-walled studio overlooking the moonlit London skyline,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    but inside, it is blindingly bright. The red &#34;ON AIR&#34; tally light is blazing.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Jaz is standing up, not sitting, bouncing on the balls of their heels
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    to the rhythm of a thumping backing track.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Their hands fly across the faders on a massive mixing desk.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    It is a chaotic, caffeine-fueled cockpit designed to wake up an entire nation.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    ### DIRECTOR&#39;S NOTES
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Style:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * The &#34;Vocal Smile&#34;: You must hear the grin in the audio.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      The soft palate is always raised to keep the tone bright, sunny, and explicitly inviting.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Dynamics: High projection without shouting. Punchy consonants and elongated vowels on excitement words.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Accent: Jaz is a DJ from Brixton, London
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Pace: Speaks at an energetic pace, keeping up with the fast music.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Speaks with a &#34;bouncing&#34; cadence. High-speed delivery with fluid transitions—no dead air, no gaps.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    ### SAMPLE CONTEXT
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Jaz is the industry standard for Top 40 radio, high-octane event promos,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    or any script that requires a charismatic Estuary accent and 11/10 infectious energy.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    #### TRANSCRIPT
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    [excitedly] Yes, massive vibes in the studio!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You are locked in and it is absolutely popping off in London right now.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    If you&#39;re stuck on the tube, or just sat there pretending to work... stop it.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Seriously, I see you.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    [shouting] Turn this up! We&#39;ve got the project roadmap landing in three, two... let&#39;s go!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>;<span style="color:#bbb">
</span></span></span></code></pre></div><p>Feel free to just try the with the audio
<a href="https://ai.google.dev/gemini-api/docs/speech-generation#transcript-tags"><code>[tags]</code></a>,
it goes already far enough.</p>
<p>Now, it&rsquo;s time to create the request, pass the model, prompt, output modalities (i.e. audio!),
and also speech config to chose the
<a href="https://ai.google.dev/gemini-api/docs/speech-generation#voices">voice</a>
and <a href="https://ai.google.dev/gemini-api/docs/speech-generation#languages">language</a>.
But let&rsquo;s not forget the streaming setting to stream the answer as soon as it&rsquo;s generated:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3.1-flash-tts-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(prompt)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">AUDIO</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">speechConfig</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SpeechConfig(<span style="color:#4070a0">&#34;Algenib&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;en-GB&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">stream</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div>
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>Gemini 3.1 TTS is not a <em>streaming model</em> like the Gemini Live model.
So it&rsquo;s generating the audio and sends it when it&rsquo;s ready.
But the idea of setting streaming here, is to start streaming the audio as soon as we start receiving it.</p>
      </div>
    </div><p>We use the <code>client.stream()</code> method to consume Server-Sent Events (SSE),
and open up the local audio system&rsquo;s data line to serve chunks of audio as they are generated:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Stream<span style="color:#666">&lt;</span>Events<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>eventStream<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">stream</span>(request))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AudioFormat<span style="color:#bbb"> </span>format<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>AudioFormat(24000,<span style="color:#bbb"> </span>16,<span style="color:#bbb"> </span>1,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>DataLine.<span style="color:#4070a0">Info</span><span style="color:#bbb"> </span>info<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>DataLine.<span style="color:#4070a0">Info</span>(SourceDataLine.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span>format);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Obtain a SourceDataLine connected to the system&#39;s active audio output</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(SourceDataLine<span style="color:#bbb"> </span>line<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(SourceDataLine)<span style="color:#bbb"> </span>AudioSystem.<span style="color:#4070a0">getLine</span>(info))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>line.<span style="color:#4070a0">open</span>(format);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>line.<span style="color:#4070a0">start</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Decode base64 bytes dynamically and pipe to the speakers</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>eventStream.<span style="color:#4070a0">forEach</span>(event<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(event<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>Events.<span style="color:#4070a0">ContentDelta</span><span style="color:#bbb"> </span>cd<span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span>cd.<span style="color:#4070a0">delta</span>()<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>Events.<span style="color:#4070a0">AudioDelta</span><span style="color:#bbb"> </span>audioDelta)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>audioData<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Base64.<span style="color:#4070a0">getDecoder</span>().<span style="color:#4070a0">decode</span>(audioDelta.<span style="color:#4070a0">data</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>line.<span style="color:#4070a0">write</span>(audioData,<span style="color:#bbb"> </span>0,<span style="color:#bbb"> </span>audioData.<span style="color:#4070a0">length</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>line.<span style="color:#4070a0">drain</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(Exception<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>e.<span style="color:#4070a0">printStackTrace</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="results">Results</h2>
<p>Let&rsquo;s listen!
What do you think of the expressivity and tone of the voice?
Pretty good, right?</p>
<figure class="audio">
<audio controls preload="metadata"  >
<source src="/mp3/generated-audio-31.wav" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</figure>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Simple Coding Agent in a Loop with LangChain4j, Jbang, and Gemini</title><link>https://glaforge.dev/posts/2026/04/11/a-simple-coding-agent-in-a-loop-with-langchain4j-jbang-and-gemini/</link><pubDate>Sat, 11 Apr 2026 23:19:24 +0200</pubDate><guid>https://glaforge.dev/posts/2026/04/11/a-simple-coding-agent-in-a-loop-with-langchain4j-jbang-and-gemini/</guid><description>&lt;p>A few days ago, Max Rydahl Andersen published a
&lt;a href="https://xam.dk/blog/nanocode-coding-agent-in-260-lines-of-java/">fascinating article&lt;/a>
about &lt;a href="https://github.com/maxandersen/nanocode">&lt;strong>nanocode&lt;/strong>&lt;/a>:
a minimalist Claude Code alternative implemented in just 260 lines of Java (inspired from a 250-line Python equivalent).
It was a masterclass in &amp;ldquo;leanness,&amp;rdquo; using raw HTTP calls and Jackson JSON parsing,
an OpenRouter or Anthropic LLM endpoint, to create an autonomous coding loop.&lt;/p>
&lt;p>I loved the concept, but I had a very practical motivation to take it in a different direction:
&lt;strong>I don&amp;rsquo;t have a Claude subscription.&lt;/strong> &amp;#x1f603;&lt;/p></description><content:encoded>
<![CDATA[<p>A few days ago, Max Rydahl Andersen published a
<a href="https://xam.dk/blog/nanocode-coding-agent-in-260-lines-of-java/">fascinating article</a>
about <a href="https://github.com/maxandersen/nanocode"><strong>nanocode</strong></a>:
a minimalist Claude Code alternative implemented in just 260 lines of Java (inspired from a 250-line Python equivalent).
It was a masterclass in &ldquo;leanness,&rdquo; using raw HTTP calls and Jackson JSON parsing,
an OpenRouter or Anthropic LLM endpoint, to create an autonomous coding loop.</p>
<p>I loved the concept, but I had a very practical motivation to take it in a different direction:
<strong>I don&rsquo;t have a Claude subscription.</strong> &#x1f603;</p>
<p>Instead, I’m a heavy user of Google’s ecosystem (who would have guessed) and I really wanted to use <strong>Gemini</strong>.
This led me to explore how much it would look like if I could integrate <a href="https://docs.langchain4j.dev/">LangChain4j</a>
and its first-class support for <strong>Google AI Gemini</strong>.</p>
<p>In this post, I’ll walk through the two variants I built, the architectural trade-offs between them,
and how I evolved the code from its original fork with the help of <a href="https://geminicli.com/"><strong>Gemini CLI</strong></a>
and its powerful <a href="https://geminicli.com/docs/cli/plan-mode/"><strong>plan mode</strong></a>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition danger">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>A Major Disclaimer on Security</span>
      </div>
      <div class="admonition-content">
        <p>Before we go further, we need to address the elephant in the room: <strong>Security.</strong></p>
<p>A basic coding agent like this is <strong>potentially dangerous</strong>. It has:</p>
<ul>
<li><strong>No sandboxing</strong>: It runs directly on your machine with your user permissions.</li>
<li><strong>No security checks</strong>: There is no <em>&ldquo;human-in-the-loop&rdquo;</em> to validate shell commands before they execute.</li>
<li><strong>Full system access</strong>: If the LLM decides to run <code>rm -rf /</code>, this script will happily try to do it.</li>
</ul>
<p><strong>This is <em>&ldquo;run at your own risk&rdquo;</em> territory.</strong> Do not use this on your production code
or any sensitive machine without further sandboxing (like Docker or a VM) and strict security measures.
This is exactly what differentiates a professional coding agent
(like Claude Code or <a href="https://geminicli.com/">Gemini CLI</a>) from a 300-line <em>&ldquo;toy&rdquo;</em> project like this one.</p>
      </div>
    </div><hr />
<h2 id="what-exactly-is-a-coding-agent">What exactly is a &ldquo;Coding Agent&rdquo;?</h2>
<p>Strip away the marketing fluff and a coding agent is essentially a <strong>persistent <code>while</code> loop</strong>.</p>
<ol>
<li>It waits for a request from the user.</li>
<li>It sends that request to an LLM along with a set of <strong>tools</strong> (functions the agent can run locally or that invoke remote APIs).</li>
<li>The LLM decides which tools to call to achieve the goal (e.g., &ldquo;Read this file&rdquo;, &ldquo;Run this test&rdquo;, &ldquo;Write this function&rdquo;).</li>
<li>The loop executes those tools, feeds the results back to the LLM, and repeats until the goal is achieved.</li>
</ol>
<p>This <em>&ldquo;agentic action&rdquo;</em> is what distinguishes an agent from a simple chatbot.
It doesn&rsquo;t just talk about code; it actively works on your filesystem to solve the problem you set forth.</p>
<h2 id="the-foundation-java-25-and-gemini-3">The Foundation: Java 25 and Gemini 3</h2>
<p>Both variants are written as single-file <a href="https://jbang.dev">JBang</a> scripts
and leverage <strong>Java 25</strong> preview features—specifically
<strong>Implicitly Declared Classes</strong> (the bare <code>void main()</code> method)
and the new <strong><code>java.lang.IO</code></strong> class (for friendly <code>IO.println()</code>/<code>readln()</code> shortcuts).</p>
<p>Thanks to the new <code>IO</code> class, the main loop is incredibly lean:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>input<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>readln(<span style="color:#4070a0">&#34;❯ &#34;</span>);<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Modern Java 25 input</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(input<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">||</span><span style="color:#bbb"> </span>input.<span style="color:#4070a0">equals</span>(<span style="color:#4070a0">&#34;/q&#34;</span>))<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>assistant.<span style="color:#4070a0">chat</span>(input);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>println(<span style="color:#4070a0">&#34;\n⏺ &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>markdown(response));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The model of choice is <strong><code>gemini-3-flash-preview</code></strong>. Gemini 3 introduces <em>&ldquo;thinking&rdquo;</em> capabilities
and <em>&ldquo;thought signatures&rdquo;</em>, which are essential for stable tool-calling in long-running agentic conversations.</p>

    <div class="admonition idea">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Advice</span>
      </div>
      <div class="admonition-content">
        <p>On some Coding/SWE-focused benchmarks Gemini 3 Flash is often just as good as Gemini 3 Pro, but faster!
So don&rsquo;t hesitate to use this super fast model! And reserve Pro for more complex reasoning scenarios.</p>
      </div>
    </div><h2 id="two-approaches-to-agentic-design">Two Approaches to Agentic Design</h2>
<p>I implemented two distinct variants to explore using LangChain4j for coding agents.</p>
<p><figure>
  <a href="#img-6bf5bd3084fbc93b852ee57c80e67416">
    <img src="/img/gemini-cli/aiservice-vs-agentic-supervisor.jpg"
      alt="Monolithic vs. Multi-Agent diagram"
       />
  </a>
  <figcaption>Monolithic vs. Multi-Agent diagram</figcaption>
</figure>
<div class="lightbox" id="img-6bf5bd3084fbc93b852ee57c80e67416">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/aiservice-vs-agentic-supervisor.jpg"
    alt="Monolithic vs. Multi-Agent diagram"
     />
  <div class="lightbox-caption">Monolithic vs. Multi-Agent diagram</div>
</div>
</p>
<h3 id="1-the-monolithic-agent-nanocode_basicjava">1. The Monolithic Agent (<code>nanocode_basic.java</code>)</h3>
<p>This version uses the tried-and-true <strong><code>AiServices</code></strong> pattern.
It’s a <strong>single agent</strong> that is directly &ldquo;wired&rdquo; to a <strong>set of tools</strong> (read, write, bash, etc.).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(Assistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatMemory</span>(MessageWindowChatMemory.<span style="color:#4070a0">withMaxMessages</span>(20))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Tools())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="2-the-multi-agent-supervisor-nanocode_agenticjava">2. The Multi-Agent Supervisor (<code>nanocode_agentic.java</code>)</h3>
<p>This variant uses the experimental <strong><code>langchain4j-agentic</code></strong> module.
Instead of one agent with twenty tools, we have a <strong>Supervisor</strong> orchestrating specialized specialists.
Each specialist agent has a narrower set of tools.
Not all the tools, just the useful ones for the task at hand.</p>
<p>To get the idea, here is a simplified look at how the sub-agents and supervisor are structured and wired together:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// 1. Specialized Tool Sets</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">FileTools</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">read</span>(String<span style="color:#bbb"> </span>path,<span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>offset,<span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>limit)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>...<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">write</span>(String<span style="color:#bbb"> </span>path,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>content)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>...<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SystemTools</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">bash</span>(String<span style="color:#bbb"> </span>cmd,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>dir)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>...<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 2. Sub-Agent Interfaces</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">FileAgent</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Agent</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;file_specialist&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">work</span>(<span style="color:#555;font-weight:bold">@V</span>(<span style="color:#4070a0">&#34;task&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>task);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">SystemAgent</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Agent</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;system_specialist&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">work</span>(<span style="color:#555;font-weight:bold">@V</span>(<span style="color:#4070a0">&#34;task&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>task);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 3. Wiring it all together</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>fileAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AgenticServices.<span style="color:#4070a0">agentBuilder</span>(FileAgent.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatModel</span>(model).<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>FileTools()).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>systemAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AgenticServices.<span style="color:#4070a0">agentBuilder</span>(SystemAgent.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatModel</span>(model).<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SystemTools()).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>SupervisorAgent<span style="color:#bbb"> </span>supervisor<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AgenticServices.<span style="color:#4070a0">supervisorBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">subAgents</span>(fileAgent,<span style="color:#bbb"> </span>systemAgent,<span style="color:#bbb"> </span>webSearchAgent)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseStrategy</span>(SupervisorResponseStrategy.<span style="color:#4070a0">SUMMARY</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li><strong>Pros</strong>: Each sub-agent has a narrower context and higher accuracy.</li>
<li><strong>Cons</strong>: The module is still experimental and adds orchestration overhead.</li>
</ul>
<hr />
<h2 id="implementing-the-tools">Implementing the Tools</h2>
<p>The tools themselves are simple POJOs with methods annotated with <code>@Tool</code>. Here is an example of the <code>read</code> tool, which reads a file with line numbers and provides a nice console log so you can see exactly what the agent is doing:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Read file with line numbers&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">read</span>(<span style="color:#555;font-weight:bold">@P</span>(<span style="color:#4070a0">&#34;Path to the file&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>path,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                   </span><span style="color:#555;font-weight:bold">@P</span>(<span style="color:#4070a0">&#34;Start line&#34;</span>)<span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>offset,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                   </span><span style="color:#555;font-weight:bold">@P</span>(<span style="color:#4070a0">&#34;Limit&#34;</span>)<span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>limit)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>IOException<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>println(<span style="color:#4070a0">&#34;\n⏺ Read(&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;)&#34;</span>);<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Visual feedback</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>lines<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>readAllLines(Path.<span style="color:#4070a0">of</span>(path));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... logic to format lines with numbers ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>formattedContent;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>All these annotations instruct the LLM what the purpose of each tool is, what each parameter means.
No need to write tool&rsquo;s JSON schemas by hand.</p>
<hr />
<h2 id="adding-some-personal-touches">Adding some &ldquo;Personal Touches&rdquo;</h2>
<p>While I moved away from the &ldquo;smallest LOC / least dependencies&rdquo; goal, I wanted to keep the script concise
while adding features that genuinely improve the CLI experience.</p>
<h3 id="ansi-markdown-rendering">ANSI Markdown Rendering</h3>
<p>Reading raw Markdown strings in a terminal is a chore. I added a <code>markdown()</code> method inspired
by a routine I shared in a <a href="https://glaforge.dev/posts/2025/02/27/pretty-print-markdown-on-the-console/">previous article</a>,
which uses regex patterns to transform Markdown syntax into <strong>ANSI escape codes</strong>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">markdown</span>(String<span style="color:#bbb"> </span>md)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>md<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\*\\*(.*?)\\*\\*&#34;</span>,<span style="color:#bbb"> </span>BOLD<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;$1&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>RESET)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Bold</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\*(.*?)\\*&#34;</span>,<span style="color:#bbb"> </span>ITALIC<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;$1&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>RESET)<span style="color:#bbb">     </span><span style="color:#60a0b0;font-style:italic">// Italic</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;(?s)```(\\w+)?\\n(.*?)\\n```&#34;</span>,<span style="color:#bbb"> </span>CODE_BG<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;$2&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>RESET)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Code blocks</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... more regex rules ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="built-in-web-search">Built-in Web Search</h3>
<p>A coding agent is only as up-to-date as its knowledge cut-off date.
I added a <code>websearch</code> tool that leverages Gemini&rsquo;s native <strong>Google Search</strong> capability,
so that the coding agent could search the web for the latest information
(for example, finding the last version of a dependency in Maven Central, how to use the last JDK enhancement&hellip;)</p>
<p>I created a dedicated sub-agent that takes care of the searches, simply by calling the Gemini model with Google Search enabled:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SearchTools</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Search the web using Google Search&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">search</span>(String<span style="color:#bbb"> </span>query)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>searchModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">apiKey</span>(GEMINI_KEY)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">allowGoogleSearch</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Native Gemini Search</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>searchModel.<span style="color:#4070a0">chat</span>(query);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>Whether you prefer the stability of a monolithic agent
or the sophisticated orchestration of a multi-agent system,
LangChain4j makes building these tools remarkably accessible.
By combining it with the reasoning power of Gemini 3 and the modern features of Java 25,
you can build a cool little coding assistant in a single file
(thanks to <strong>JBang’s</strong> ability to handle dependencies and execution without the boilerplate of a project build file).</p>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>You can find both implementations in my fork here:</p>
<p><a href="https://github.com/glaforge/nanocode">github.com/glaforge/nanocode</a></p>
      </div>
    </div><p>I’m curious to see what others will do with this experiment.
What would you add next? How would you do it differently?
I&rsquo;d love to see more forks that continue to explore this space while keeping the coding agent small enough to fit in a single file.</p>
<p>Happy coding!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>An ADK Java agent powered by Gemma 4</title><link>https://glaforge.dev/posts/2026/04/02/an-adk-java-agent-powered-by-gemma-4/</link><pubDate>Thu, 02 Apr 2026 16:45:10 +0200</pubDate><guid>https://glaforge.dev/posts/2026/04/02/an-adk-java-agent-powered-by-gemma-4/</guid><description>&lt;p>Today, DeepMind announced the
&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/gemma-4/">release of Gemma 4&lt;/a>,
a very impressive and powerful new version of the
&lt;a href="https://deepmind.google/models/gemma/">Gemma family of models&lt;/a>.
As I&amp;rsquo;ve been contributing to &lt;a href="https://adk.dev/">ADK Java&lt;/a> a fair bit recently,
I was curious to see how I would configure ADK Java agents to work with Gemma 4.&lt;/p>
&lt;p>In this article, we&amp;rsquo;ll explore 3 paths:&lt;/p>
&lt;ul>
&lt;li>Calling the AI Studio API surface directly,&lt;/li>
&lt;li>Calling Gemma 4 hosted via a vLLM instance thanks to the
&lt;a href="https://developers.googleblog.com/en/adk-for-java-opening-up-to-third-party-language-models-via-langchain4j-integration/">LangChain4j bridge&lt;/a>.&lt;/li>
&lt;li>Calling Gemma 4 locally via Ollama&lt;/li>
&lt;/ul>
&lt;p>With the appropriate model weights format, we&amp;rsquo;ll also be able to run Gemma 4 locally via Ollama.
But that&amp;rsquo;s for another day.&lt;/p></description><content:encoded>
<![CDATA[<p>Today, DeepMind announced the
<a href="https://blog.google/innovation-and-ai/technology/developers-tools/gemma-4/">release of Gemma 4</a>,
a very impressive and powerful new version of the
<a href="https://deepmind.google/models/gemma/">Gemma family of models</a>.
As I&rsquo;ve been contributing to <a href="https://adk.dev/">ADK Java</a> a fair bit recently,
I was curious to see how I would configure ADK Java agents to work with Gemma 4.</p>
<p>In this article, we&rsquo;ll explore 3 paths:</p>
<ul>
<li>Calling the AI Studio API surface directly,</li>
<li>Calling Gemma 4 hosted via a vLLM instance thanks to the
<a href="https://developers.googleblog.com/en/adk-for-java-opening-up-to-third-party-language-models-via-langchain4j-integration/">LangChain4j bridge</a>.</li>
<li>Calling Gemma 4 locally via Ollama</li>
</ul>
<p>With the appropriate model weights format, we&rsquo;ll also be able to run Gemma 4 locally via Ollama.
But that&rsquo;s for another day.</p>
<h2 id="1--the-easy-case-gemma-4-on-ai-studio">1 — The Easy Case: Gemma 4 on AI Studio</h2>
<p>If you&rsquo;re using Gemma 4 via the Google AI Studio API surface,
you have to use the <code>Gemini</code> model builder and reference the model name:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Gemini<span style="color:#bbb"> </span>gemma4<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Gemini.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemma-4-31b-it&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(gemma4)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... instructions and tools</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, Gemma 4 is exposed the same way as the Gemini models, via the same API surface.
That&rsquo;s why the model is an instance of <code>Gemini</code>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Tip</span>
      </div>
      <div class="admonition-content">
        <p>In an upcoming release of ADK, we&rsquo;ll also be able to simplify the above by just setting the model string like we do for Gemini models:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemma-4-31b-it&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... instructions and tools</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div>
      </div>
    </div><h2 id="2--calling-a-vllm-hosted-gemma-4-via-langchain4j">2 — Calling a vLLM hosted Gemma 4 via LangChain4j</h2>
<p>During the beta testing period, internally at Google,
my colleague <a href="https://x.com/vladkol">Vlad</a> was exposing the Gemma 4 model weights
via <a href="https://docs.vllm.ai/">vLLM</a>, running inside a Google
<a href="https://docs.cloud.google.com/run/docs/configuring/services/gpu">Cloud Run instance with GPU</a>.
And I was using his endpoint to test Gemma 4 &#x1f609;</p>
<p>However, vLLM features an OpenAI-compatible API.
So Gemma 4 on vLLM needs to be called with that API surface, not with the Gemini one.</p>
<p>Fortunately, with the LangChain4j bridge I developed last year, you can configure OpenAI-compatible models,
thanks to the <code>OpenAiChatModel</code> (or the streaming variant) chat model
from <a href="https://docs.langchain4j.dev/">LangChain4j</a> to connect to the vLLM server.</p>
<h3 id="creating-a-simple-agent">Creating a Simple Agent</h3>
<p>First, we need to configure the <code>OpenAiChatModel</code> (or <code>OpenAiStreamingChatModel</code>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>OpenAiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gg-hf-gg/gemma-4-31b-it&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(<span style="color:#4070a0">&#34;YOUR_API_KEY&#34;</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// A dummy key if not required by your vLLM setup</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">baseUrl</span>(<span style="color:#4070a0">&#34;https://your-vllm-instance/v1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">timeout</span>(Duration.<span style="color:#4070a0">ofMinutes</span>(5))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">customParameters</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;chat_template_kwargs&#34;</span>,<span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;enable_thinking&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div>
    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>For function calling (tool use) to work correctly with Gemma 4 on vLLM, as we shall see in further examples,
you <strong>must</strong> enable the <em>thinking capability</em> in the <em>chat template</em>.
This is done via the <code>chat_template_kwargs</code> / <code>enable_thinking</code> parameter,
which enables thinking but also function calling at the same time.</p>
      </div>
    </div>
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>I&rsquo;ve defined a long timeout, as the cold start to load the weights in memory can take up to 4 minutes!
But once the Cloud Run instance is <em>hot</em>, Gemma 4 replies instantly.</p>
      </div>
    </div><p>Let&rsquo;s have a look at a simple science teacher agent:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>teacherAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-teacher&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(LangChain4j.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gg-hf-gg/gemma-4-31b-it&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a friendly science teacher
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        who explains concepts simply.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We use the <code>LangChain4j.builder()</code> to wrap the OpenAI compatible chat model
as a Java class extending ADK&rsquo;s <code>BaseLlm</code> class, which is the parent class of all LLMs supported by ADK.</p>
<h3 id="adding-tools-local-java-functions">Adding Tools (Local Java Functions)</h3>
<p>Gemma 4&rsquo;s reasoning capabilities shine when you add tools.
You can expose any Java method as a tool using ADK&rsquo;s <code>FunctionTool</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>orderAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;order-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(LangChain4j.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gg-hf-gg/gemma-4-31b-it&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Use the `lookup_order` tool to retrieve order details.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(FunctionTool.<span style="color:#4070a0">create</span>(<span style="color:#007020;font-weight:bold">this</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;retrieveOrder&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Annotations.Schema</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;lookup_order&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Retrieve order details by ID&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">retrieveOrder</span>(String<span style="color:#bbb"> </span>orderId)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Your database logic here...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;out_for_delivery&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>In this example, we reference a local Java function to lookup order details,
so Gemma 4 can call it should the user ask for the status of their order.</p>
<h2 id="3--calling-gemma-4-locally-via-ollama">3 — Calling Gemma 4 locally via Ollama</h2>
<p>It&rsquo;s also possible to take on a third path, with <a href="https://ollama.com/library/gemma4">Ollama&rsquo;s Gemma 4 support</a>.
Thanks to the LangChain4j bridge again, you can configure Gemma 4 with the following LangChain4j chat model definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>OllamaChatModel<span style="color:#bbb"> </span>ollamaChatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>OllamaChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemma4:e4b&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>.<span style="color:#4070a0">baseUrl</span>(<span style="color:#4070a0">&#34;http://127.0.0.1:11434&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="wrapping-up">Wrapping up</h2>
<p>That&rsquo;s about it for today!
With ADK Java and Gemma 4, you have a powerful, flexible, and open-weight foundation for your next AI agent project! &#x1f916;
Thanks to the LangChain4j / ADK bridge, it&rsquo;s even possible to invoke Gemma via different API surfaces than Gemini&rsquo;s.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>As a reminder, we&rsquo;ve just
<a href="https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/">announced ADK Java 1.0</a>,
if you want to have a refresher about the latest features and enhancements to the project.</p>
<p>And you can watch this YouTube video I recorded that goes through the new features,
as well as a concrete ADK agent called &ldquo;Comic Trip&rdquo; that transforms travel photography into vintage pop-art comic illustrations.
Go check out the
<a href="content/posts/2026/03/30/building-my-comic-trip-agent-with-adk-java-1-0.md">behind-the-scene article</a> on how I built it.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/YqABMjSho_M?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

      </div>
    </div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Creating a Wikipedia MCP Server in Java in a Few Prompts with Skills</title><link>https://glaforge.dev/posts/2026/04/02/creating-a-wikipedia-mcp-server-in-java-in-a-few-prompts/</link><pubDate>Thu, 02 Apr 2026 10:17:27 +0200</pubDate><guid>https://glaforge.dev/posts/2026/04/02/creating-a-wikipedia-mcp-server-in-java-in-a-few-prompts/</guid><description>&lt;p>Since I started using &lt;a href="https://modelcontextprotocol.io/">Model Context Protocol (MCP)&lt;/a> to equip my AI agents with useful tools,
I&amp;rsquo;ve been looking for ways to quickly build and iterate on local servers.
A few weeks ago, I shared
&lt;a href="https://glaforge.dev/posts/2026/02/21/easily-build-a-local-mcp-server-in-java-with-a-skill-in-gemini-cli/">how to easily build a local MCP server in Java with a custom skill in Gemini CLI&lt;/a>.
Today, I wanted to put that skill to the test by creating a &lt;strong>Wikipedia MCP server&lt;/strong>.&lt;/p>
&lt;p>What&amp;rsquo;s impressive is that I didn&amp;rsquo;t even have to leave my terminal or read documentation.
The entire process was a conversation with &lt;a href="https://geminicli.com">Gemini CLI&lt;/a>, leveraging its ability to search the web, find libraries, and even check migration guides!&lt;/p></description><content:encoded>
<![CDATA[<p>Since I started using <a href="https://modelcontextprotocol.io/">Model Context Protocol (MCP)</a> to equip my AI agents with useful tools,
I&rsquo;ve been looking for ways to quickly build and iterate on local servers.
A few weeks ago, I shared
<a href="https://glaforge.dev/posts/2026/02/21/easily-build-a-local-mcp-server-in-java-with-a-skill-in-gemini-cli/">how to easily build a local MCP server in Java with a custom skill in Gemini CLI</a>.
Today, I wanted to put that skill to the test by creating a <strong>Wikipedia MCP server</strong>.</p>
<p>What&rsquo;s impressive is that I didn&rsquo;t even have to leave my terminal or read documentation.
The entire process was a conversation with <a href="https://geminicli.com">Gemini CLI</a>, leveraging its ability to search the web, find libraries, and even check migration guides!</p>
<h2 id="the-interactive-process">The Interactive Process</h2>
<p>I started by asking Gemini CLI about the Wikipedia API.
Instead of guessing, I used the <code>@search</code> command to find the exact <em>&ldquo;contracts&rdquo;</em> for searching and retrieving pages.</p>
<p>The conversation went something like this:</p>
<ol>
<li>
<p><strong>Exploring the API</strong>: I asked <code>@search what is the contract for the Wikipedia API to search for Wikipedia pages?</code>. Gemini found the modern <strong>Wikimedia REST API</strong> (<code>/search/page</code>) and the older Action API.
<figure>
  <a href="#img-64a81fd5cd10fbc36493595ae8d2ce83">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-1.jpg"
      alt="Using @search in Gemini CLI to find information about the Wikipedia API"
       />
  </a>
  <figcaption>Using @search in Gemini CLI to find information about the Wikipedia API</figcaption>
</figure>
<div class="lightbox" id="img-64a81fd5cd10fbc36493595ae8d2ce83">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-1.jpg"
    alt="Using @search in Gemini CLI to find information about the Wikipedia API"
     />
  <div class="lightbox-caption">Using @search in Gemini CLI to find information about the Wikipedia API</div>
</div>
</p>
</li>
<li>
<p><strong>Retrieving Content</strong>: I then asked how to get the actual page content. It identified the <code>/page/html/{title}</code> endpoint as the best way to get clean HTML.
<figure>
  <a href="#img-99cfb0fd769ba27247e6a98996bb39b5">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-2.jpg"
      alt="Gemini CLI screenshot showing the search about the full Wikipedia page retrieval"
       />
  </a>
  <figcaption>Gemini CLI screenshot showing the search about the full Wikipedia page retrieval</figcaption>
</figure>
<div class="lightbox" id="img-99cfb0fd769ba27247e6a98996bb39b5">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-2.jpg"
    alt="Gemini CLI screenshot showing the search about the full Wikipedia page retrieval"
     />
  <div class="lightbox-caption">Gemini CLI screenshot showing the search about the full Wikipedia page retrieval</div>
</div>
</p>
</li>
<li>
<p><strong>Finding a Converter</strong>: Since LLMs prefer Markdown over raw HTML (returned by the Wikipedia API), I searched for a Java library: <code>@search how to render HTML to Markdown in Java?</code>. It suggested <strong>CopyDown</strong> (a Java port of Turndown) as the simplest option.
<figure>
  <a href="#img-ebb2484935ce04d010f2dc903cb597bc">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-3.jpg"
      alt="Gemini CLI interactive session on how to find an HTML to Markdown converter"
       />
  </a>
  <figcaption>Gemini CLI interactive session on how to find an HTML to Markdown converter</figcaption>
</figure>
<div class="lightbox" id="img-ebb2484935ce04d010f2dc903cb597bc">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-3.jpg"
    alt="Gemini CLI interactive session on how to find an HTML to Markdown converter"
     />
  <div class="lightbox-caption">Gemini CLI interactive session on how to find an HTML to Markdown converter</div>
</div>
</p>
</li>
</ol>
<p>One particularly &ldquo;pro&rdquo; move from Gemini: it noticed I wanted to use <strong>Jackson</strong> for JSON parsing.
It proactively searched for the latest version, found that <strong>Jackson 3.0.0-rc4</strong> was just released,
and even checked the <a href="https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md">migration guide</a>
to ensure the new <code>tools.jackson</code> package names were used correctly!</p>
<p>Then, I asked Gemini CLI to use my JBang / LangChain4j MCP server creation skill to generate the code of my Wikipedia STDIO MCP server:
<figure>
  <a href="#img-3127413bc24c5d5f1d03688a87a3b9f8">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-4.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3127413bc24c5d5f1d03688a87a3b9f8">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-4.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="the-resulting-java-code">The Resulting Java Code</h2>
<p>Once we had the blueprint, it triggered my <code>jbang-mcp-server</code> skill.
It scaffolded the following <a href="https://jbang.dev/">JBang</a> script,
combining <a href="https://github.com/langchain4j/langchain4j">LangChain4j</a>&rsquo;s MCP support with our chosen libraries.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">///usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS dev.langchain4j:langchain4j-core:1.11.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS dev.langchain4j:langchain4j-community-mcp-server:1.11.0-beta19</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS org.slf4j:slf4j-simple:2.0.17</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS io.github.furstenheim:copy_down:1.1</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS tools.jackson.core:jackson-databind:3.0.0-rc4</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//JAVA 21</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.agent.tool.Tool</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.community.mcp.server.McpServer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.community.mcp.server.transport.StdioMcpServerTransport</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.slf4j.Logger</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.slf4j.LoggerFactory</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.github.furstenheim.CopyDown</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">tools.jackson.databind.ObjectMapper</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">tools.jackson.databind.JsonNode</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">tools.jackson.databind.json.JsonMapper</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.URI</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.http.HttpClient</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.http.HttpRequest</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.http.HttpResponse</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.List</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.concurrent.CountDownLatch</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.URLEncoder</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.nio.charset.StandardCharsets</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">WikipediaMcpServer</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Configure SLF4J Simple Logger to write to System.err</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// This is crucial for MCP servers over STDIO to avoid polluting stdout</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">setProperty</span>(<span style="color:#4070a0">&#34;org.slf4j.simpleLogger.logFile&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;System.err&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>Logger<span style="color:#bbb"> </span>log<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LoggerFactory.<span style="color:#4070a0">getLogger</span>(WikipediaMcpServer.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>Exception<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Starting Wikipedia MCP Server...&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>WikipediaTools<span style="color:#bbb"> </span>tools<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WikipediaTools();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>McpServer<span style="color:#bbb"> </span>server<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpServer(List.<span style="color:#4070a0">of</span>(tools));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>StdioMcpServerTransport<span style="color:#bbb"> </span>transport<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>StdioMcpServerTransport(server);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;MCP Server started successfully on STDIO.&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>CountDownLatch(1).<span style="color:#4070a0">await</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">WikipediaTools</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>HttpClient<span style="color:#bbb"> </span>httpClient<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>HttpClient.<span style="color:#4070a0">newBuilder</span>().<span style="color:#4070a0">followRedirects</span>(HttpClient.<span style="color:#4070a0">Redirect</span>.<span style="color:#4070a0">NORMAL</span>).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>ObjectMapper<span style="color:#bbb"> </span>objectMapper<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>JsonMapper.<span style="color:#4070a0">builder</span>().<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>CopyDown<span style="color:#bbb"> </span>copyDown<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>CopyDown();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>USER_AGENT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;WikipediaMcpServer/1.0 (contact@example.com)&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Search Wikipedia for a given query and return a list of matching page titles and brief descriptions. Use this to find the exact page title before retrieving content.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">searchWikipedia</span>(String<span style="color:#bbb"> </span>query)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>Exception<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Searching Wikipedia for: {}&#34;</span>,<span style="color:#bbb"> </span>query);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>encodedQuery<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>URLEncoder.<span style="color:#4070a0">encode</span>(query,<span style="color:#bbb"> </span>StandardCharsets.<span style="color:#4070a0">UTF_8</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>url<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;https://en.wikipedia.org/w/rest.php/v1/search/page?q=&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>encodedQuery<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&amp;limit=5&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>HttpRequest<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>HttpRequest.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">uri</span>(URI.<span style="color:#4070a0">create</span>(url))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">header</span>(<span style="color:#4070a0">&#34;User-Agent&#34;</span>,<span style="color:#bbb"> </span>USER_AGENT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">GET</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>HttpResponse<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>httpClient.<span style="color:#4070a0">send</span>(request,<span style="color:#bbb"> </span>HttpResponse.<span style="color:#4070a0">BodyHandlers</span>.<span style="color:#4070a0">ofString</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(response.<span style="color:#4070a0">statusCode</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span>200)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Error searching Wikipedia: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">statusCode</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34; - &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">body</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>JsonNode<span style="color:#bbb"> </span>rootNode<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>objectMapper.<span style="color:#4070a0">readTree</span>(response.<span style="color:#4070a0">body</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>JsonNode<span style="color:#bbb"> </span>pagesNode<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>rootNode.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;pages&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>StringBuilder<span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>StringBuilder(<span style="color:#4070a0">&#34;Search Results:\n\n&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(pagesNode<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span>pagesNode.<span style="color:#4070a0">isArray</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(JsonNode<span style="color:#bbb"> </span>page<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>pagesNode)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>String<span style="color:#bbb"> </span>title<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">has</span>(<span style="color:#4070a0">&#34;title&#34;</span>)<span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;title&#34;</span>).<span style="color:#4070a0">asText</span>()<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>String<span style="color:#bbb"> </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">has</span>(<span style="color:#4070a0">&#34;description&#34;</span>)<span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;description&#34;</span>).<span style="color:#4070a0">asText</span>()<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;No description&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>result.<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;- **&#34;</span>).<span style="color:#4070a0">append</span>(title).<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;**: &#34;</span>).<span style="color:#4070a0">append</span>(description).<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;\n&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(result.<span style="color:#4070a0">length</span>()<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Search Results:\n\n&#34;</span>.<span style="color:#4070a0">length</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;No results found for query: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>query;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>result.<span style="color:#4070a0">toString</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Retrieve the content of a specific Wikipedia page by its exact title, converted to Markdown format. Use the exact title returned by searchWikipedia.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">getWikipediaPageContent</span>(String<span style="color:#bbb"> </span>title)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>Exception<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Retrieving Wikipedia page: {}&#34;</span>,<span style="color:#bbb"> </span>title);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>encodedTitle<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>URLEncoder.<span style="color:#4070a0">encode</span>(title.<span style="color:#4070a0">replace</span>(<span style="color:#4070a0">&#34; &#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;_&#34;</span>),<span style="color:#bbb"> </span>StandardCharsets.<span style="color:#4070a0">UTF_8</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>url<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;https://en.wikipedia.org/api/rest_v1/page/html/&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>encodedTitle;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>HttpRequest<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>HttpRequest.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">uri</span>(URI.<span style="color:#4070a0">create</span>(url))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">header</span>(<span style="color:#4070a0">&#34;User-Agent&#34;</span>,<span style="color:#bbb"> </span>USER_AGENT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">GET</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>HttpResponse<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>httpClient.<span style="color:#4070a0">send</span>(request,<span style="color:#bbb"> </span>HttpResponse.<span style="color:#4070a0">BodyHandlers</span>.<span style="color:#4070a0">ofString</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(response.<span style="color:#4070a0">statusCode</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span>200)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Error retrieving Wikipedia page: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">statusCode</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34; - &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">body</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>htmlContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">body</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Converting HTML to Markdown for page: {}&#34;</span>,<span style="color:#bbb"> </span>title);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>copyDown.<span style="color:#4070a0">convert</span>(htmlContent);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I didn&rsquo;t even touch the code at all, and it worked flawlessly out of the box.
I would probably only update the version dependency on LangChain4j which is not up-to-date.
But that&rsquo;s about it.</p>
<h2 id="configuring-the-mcp-server-in-gemini-cli">Configuring the MCP Server in Gemini CLI</h2>
<p>To use this server, you just need to register it in your <code>~/.gemini/settings.json</code>. Gemini CLI will then automatically launch it as a child process and communicate with it over Standard Input/Output.</p>
<p>Add the following to your <code>mcpServers</code> section:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;wikipedia-mcp&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;command&#34;</span>: <span style="color:#4070a0">&#34;jbang&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;run&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;--quiet&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;/path/to/your/WikipediaMcpServer.java&#34;</span>
</span></span><span style="display:flex;"><span>      ]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Once the MCP configuration is saved and Gemini CLI reloaded, I could check that the MCP server was available,
by running the <code>/mcp list</code> command:</p>
<p><figure>
  <a href="#img-dcbe7dff43048b8dfc4bcac68710839f">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-5.jpg"
      alt="List of available MCP servers tools in Gemini CLI"
       />
  </a>
  <figcaption>List of available MCP servers tools in Gemini CLI</figcaption>
</figure>
<div class="lightbox" id="img-dcbe7dff43048b8dfc4bcac68710839f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-5.jpg"
    alt="List of available MCP servers tools in Gemini CLI"
     />
  <div class="lightbox-caption">List of available MCP servers tools in Gemini CLI</div>
</div>
</p>
<p>There are 2 tools available: one for fetching a list of relevant pages, and the other to fetch the content of an individual page.</p>
<h2 id="putting-it-up-to-the-test">Putting it up to the test</h2>
<p>Yesterday, the &#x1f680; Artemis 2 mission launched to travel around the moon &#x1f314;
I&rsquo;m sure Wikipedia is already updated with the latest information about the status of the mission.
Let&rsquo;s double check:</p>
<p><figure>
  <a href="#img-4cefb0436db29b82956dd3ed8f364f6b">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-6.jpg"
      alt="Asking about the Artemis 2 mission"
       />
  </a>
  <figcaption>Asking about the Artemis 2 mission</figcaption>
</figure>
<div class="lightbox" id="img-4cefb0436db29b82956dd3ed8f364f6b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-6.jpg"
    alt="Asking about the Artemis 2 mission"
     />
  <div class="lightbox-caption">Asking about the Artemis 2 mission</div>
</div>
</p>
<p>It found relevant pages, then it loaded the content of those pages and found the information,
and summarized its findings:</p>
<p><figure>
  <a href="#img-27a4f68768636f19013c8be8790f8f3b">
    <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-7.jpg"
      alt="Response about the status of the Artemis 2 mission"
       />
  </a>
  <figcaption>Response about the status of the Artemis 2 mission</figcaption>
</figure>
<div class="lightbox" id="img-27a4f68768636f19013c8be8790f8f3b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/wikipedia-mcp/gcli-mcp-wikipedia-7.jpg"
    alt="Response about the status of the Artemis 2 mission"
     />
  <div class="lightbox-caption">Response about the status of the Artemis 2 mission</div>
</div>
</p>
<p>Mission in progress!
But my own personal mission over those few interactive prompts inside Gemini CLI is accomplished:
in less than 5 minutes, I had my custom MCP server to query Wikipedia!
And it took me actually more time to write this article itself!</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>My winning combo of the day: <a href="https://geminicli.com">Gemini CLI</a>, agent skills, Java,
<a href="https://www.jbang.dev/">JBang</a>, and <a href="https://docs.langchain4j.dev">LangChain4j</a>&hellip; and boom &#x1f4a5;</p>
<p>Maybe we&rsquo;ll find that obvious in a few months, but I&rsquo;m still impressed by how it is today, in just a few prompts,
to create something useful like an MCP server that your friendly AI agents can use.
Building tools for your AI agents has never been this fluid.</p>
<p>Happy hacking! 🚀
And let&rsquo;s go to the &#x1f314; and beyond!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Building my Comic Trip agent with ADK Java 1.0</title><link>https://glaforge.dev/posts/2026/03/30/building-my-comic-trip-agent-with-adk-java-1-0/</link><pubDate>Mon, 30 Mar 2026 22:33:09 +0200</pubDate><guid>https://glaforge.dev/posts/2026/03/30/building-my-comic-trip-agent-with-adk-java-1-0/</guid><description>&lt;p>I&amp;rsquo;m happy to echo here the release of &lt;strong>ADK for Java v1.0&lt;/strong>, Google&amp;rsquo;s &lt;a href="https://google.github.io/adk-docs/">Agent Development Kit&lt;/a> framework to build AI agents in Java.
I spent a lot of time on this project. I also wrote the
&lt;a href="https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/">announcement blog post&lt;/a>
on the Google for Developers blog.
And I&amp;rsquo;ve recorded this &lt;a href="https://www.youtube.com/watch?v=YqABMjSho_M">YouTube video&lt;/a> highlighting some of the new features of the framework,
in which I&amp;rsquo;m demonstrating some of them via an app I built: my &lt;strong>Comic Trip&lt;/strong> agent (pun intended).&lt;/p></description><content:encoded>
<![CDATA[<p>I&rsquo;m happy to echo here the release of <strong>ADK for Java v1.0</strong>, Google&rsquo;s <a href="https://google.github.io/adk-docs/">Agent Development Kit</a> framework to build AI agents in Java.
I spent a lot of time on this project. I also wrote the
<a href="https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/">announcement blog post</a>
on the Google for Developers blog.
And I&rsquo;ve recorded this <a href="https://www.youtube.com/watch?v=YqABMjSho_M">YouTube video</a> highlighting some of the new features of the framework,
in which I&rsquo;m demonstrating some of them via an app I built: my <strong>Comic Trip</strong> agent (pun intended).</p>
<p><figure>
  <a href="#img-5b957d6b887fa20b57aa403da25b1dee">
    <img src="/img/comic-trip/comic-trip-1.jpg"
      alt="Screenshot of the Comic Trip app"
       />
  </a>
  <figcaption>Screenshot of the Comic Trip app</figcaption>
</figure>
<div class="lightbox" id="img-5b957d6b887fa20b57aa403da25b1dee">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/comic-trip/comic-trip-1.jpg"
    alt="Screenshot of the Comic Trip app"
     />
  <div class="lightbox-caption">Screenshot of the Comic Trip app</div>
</div>
</p>
<p>The <strong>Comic Trip</strong> agent is a fun little application that transforms your travel photography into a vibrant, pop-art comic strip experience.
Beyond the visual style, it also guesses locations (thanks to Gemini) and enriches each &ldquo;panel&rdquo; with nearby points of interest (via Google Maps integration).</p>
<p>For a recap and a demo of what this application is capable of, please check out the YouTube video:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/YqABMjSho_M?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>This project is a showcase for the <strong>Agent Development Kit (ADK) for Java 1.0</strong>,
demonstrating how to build multi-agent systems with:</p>
<ul>
<li><strong>smart models</strong> — Gemini and &#x1f34c; Nano Banana,</li>
<li><strong>ADK tools</strong> — &#x1f50d; Google Search, &#x1f5fa;&#xfe0f; Google Maps,</li>
<li><strong>ADK services</strong> — Google Cloud Storage artifact service, and</li>
<li><strong>cloud-native storage integration</strong> — Google Cloud <a href="https://cloud.google.com/products/firestore">Firestore</a> database.</li>
</ul>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Source code available on GitHub</span>
      </div>
      <div class="admonition-content">
        <p>If you&rsquo;re interested in seeing the source code of this application,
please have a look at the <a href="https://github.com/glaforge/comic-trip-agent">repository on GitHub</a>.
The repository also explains how to build and deploy this application on <a href="https://cloud.google.com/run/">Google Cloud Run</a>.
And of course, you can run it locally on your machine as well.</p>
      </div>
    </div><h2 id="powered-by-adk-for-java-10">Powered by ADK for Java 1.0</h2>
<p>ADK for Java 1.0 makes it easier to build and coordinate AI agents.
In this application, I leverage several key concepts:</p>
<ul>
<li>
<p><strong>App &amp; Plugins</strong>: The entire agent hierarchy is encapsulated within an <code>App</code>.
We use the <code>LoggingPlugin</code> for seamless execution observability and debugging.
Via this <code>App</code> shell, the logging plugin is actually applied to all the sub-agents involved in the multi-agent system.
No need to configure each sub-agent individually.</p>
</li>
<li>
<p><strong>Runners &amp; Sessions</strong>: An <code>InMemoryRunner</code> manages the execution flow, while an <code>InMemorySessionService</code> ensures that the context for each user&rsquo;s trip is isolated and persistent throughout the multi-step process.</p>
</li>
<li>
<p><strong>Specialized Agents</strong>: We utilize a variety of agent types, including</p>
<ul>
<li><code>LlmAgent</code> for LLM-based tasks,</li>
<li><code>SequentialAgent</code> for step-by-step flows, and</li>
<li><code>ParallelAgent</code> for running multiple agents in parallel.</li>
</ul>
</li>
</ul>
<h2 id="multi-agent-architecture">Multi-Agent Architecture</h2>
<p>The core intelligence of the Comic Trip agent is driven by the following agent hierarchy:</p>
<ol>
<li>
<p><strong><code>picture_analyzer_agent</code> (Gemini 3 Flash)</strong>:
The entry point of our flow. It analyzes the uploaded photograph to extract a detailed description and identify the location.
It&rsquo;s impressive to see how good Gemini is at guessing the location of a landmark globally!</p>
</li>
<li>
<p><strong><code>poi_and_comic_flow</code> (Parallel Execution)</strong>:
Once the context is established, two specialized agents run in parallel:</p>
<ul>
<li>
<p><strong><code>comic_illustrator_agent</code> (Gemini 3.1 Flash Image)</strong>: This multimodal agent (also known as &#x1f34c; &ldquo;Nano Banana 2&rdquo;) transforms the original image into a pop-art masterpiece.</p>
</li>
<li>
<p><strong><code>points_of_interest_agent</code> (Gemini 2.5 Flash)</strong>: Equipped with the <strong><code>GoogleMapsTool</code></strong>, it searches for nearby attractions based on the identified location, adding depth to the travel experience.</p>
</li>
</ul>
</li>
</ol>
<p>To configure the Google Maps integration, you just need to add the <code>.tools(new GoogleMapsTool())</code> call to your agent definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>poiGoogleMapsAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;points_of_interest_agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Given the location in:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {description_and_location}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Please list points of interest (POI)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        in the area no further than a kilometer away
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        using the `google_maps` tool.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Each POI should have a name and a description.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t mention distances in your response.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        And don&#39;t start with introductory text for the list.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleMapsTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(OUTPUT_KEY_POINTS_OF_INTEREST)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>The entire process is orchestrated by a <code>SequentialAgent</code> named <code>main_flow</code>, which strictly orders the initial analysis before triggering the parallel phase.</p>
<p><figure>
  <a href="#img-e12e590203cdc56a9cd2faf1f01fee7e">
    <img src="/img/comic-trip/multi-agent-diagram.png"
      alt="Architecture diagram of the various agents and flows"
       />
  </a>
  <figcaption>Architecture diagram of the various agents and flows</figcaption>
</figure>
<div class="lightbox" id="img-e12e590203cdc56a9cd2faf1f01fee7e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/comic-trip/multi-agent-diagram.png"
    alt="Architecture diagram of the various agents and flows"
     />
  <div class="lightbox-caption">Architecture diagram of the various agents and flows</div>
</div>
</p>

    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Quick tip</span>
      </div>
      <div class="admonition-content">
        <p>The above diagram was generated by a <a href="https://glaforge.dev/posts/2025/08/01/visualizing-adk-multiagent-systems/">tool</a> that I vibe-coded a while ago,
that takes your multi-agent source code, and creates diagrams to visualize the flow of agents and their different sub-agents.</p>
      </div>
    </div><h2 id="the-visuals-multimedia-generation-and-artifacts">The Visuals: Multimedia Generation and Artifacts</h2>
<p>Generating a high-quality comic panel is just the first step.
ADK 1.0 handles the resulting multimedia artifacts as well.</p>
<p>The <code>comic_illustrator_agent</code> uses an <code>afterModelCallback</code> to intercept the generated image bytes.
These bytes are then persisted using the <strong><code>GcsArtifactService</code></strong>, which automatically handles the upload to a Google Cloud Storage bucket.
This integration ensures that generated media is stored and is easily accessible via public URLs (for the frontend):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>gs://comic-trip-picture-bucket/comic_trip_app/comic_trip_user/{tripId}/{imageId}.png/0
</span></span></code></pre></div><p>The code in the callback goes through the <code>Content</code> and <code>Part</code>s to find parts that contain the generated image (in an <code>inlineData</code> field),
and we save it as an artifact, via the <code>saveArtifact()</code> method of the <code>callbackContext</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>.<span style="color:#4070a0">afterModelCallback</span>((callbackContext,<span style="color:#bbb"> </span>llmResponse)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Maybe.<span style="color:#4070a0">fromOptional</span>(llmResponse.<span style="color:#4070a0">content</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">flatMap</span>(Content::parts)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">flatMap</span>(List::stream)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">filter</span>(part<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>part.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">isPresent</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">findFirst</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">flatMap</span>(part<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>imageId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>generateId();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>callbackContext.<span style="color:#4070a0">saveArtifact</span>(imageId<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;.png&#34;</span>,<span style="color:#bbb"> </span>part)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">blockingAwait</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Optional.<span style="color:#4070a0">of</span>(llmResponse.<span style="color:#4070a0">toBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">content</span>(Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(imageId)))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}))<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="backend-architecture-quarkus--cloud-services">Backend Architecture: Quarkus &amp; Cloud Services</h2>
<p>The application&rsquo;s web backend is built with the <a href="https://quarkus.io/"><strong>Quarkus</strong> framework</a>,
and deployed on <a href="https://cloud.google.com/run"><strong>Google Cloud Run</strong></a>.
It also serves the frontend assets.</p>
<p>When a user uploads a batch of images, the <code>MissionControlResource</code> receives the multipart request.
To ensure maximum throughput, it uses <strong>Java 21 Virtual Threads</strong> to run the <code>ComicTripAnalyzer</code> agent for each image in parallel.</p>

    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>As you might notice, I didn&rsquo;t use a <code>ParallelAgent</code> here, but let the Quarkus controller handle the parallelization for me.
An ADK parallel agent makes sense when you have a known discreet set of sub-tasks to run in parallel,
but in my case, I don&rsquo;t know in advance how many images I&rsquo;ll receive.
One approach, though, could be to create a custom sub-class of <code>BaseAgent</code> to handle the fan-in / fan-out approach
in the agent graph instead of at the level of the web controller.</p>
      </div>
    </div><p>While the images live in GCS, the trip&rsquo;s metadata and enriched details (descriptions, POIs, and image links) are stored in <strong>Google Cloud Firestore</strong>.
This saving action is also triggered at the web controller level, once all the agents have run through each image to process.</p>
<h2 id="frontend-vibe-coded-with-stitch-and-antigravity">Frontend: Vibe-Coded with Stitch and Antigravity</h2>
<p>The user interface of the Comic Trip agent was designed by <a href="https://stitch.withgoogle.com/"><strong>Google Stitch</strong></a>
and implemented using <a href="https://antigravity.google/"><strong>Antigravity</strong></a>.</p>
<p><figure>
  <a href="#img-6c503a0a71d9422592d1787fcc85726b">
    <img src="/img/comic-trip/stitch-ui.jpg"
      alt="Screenshot of the Stich user interface where the frontend was designed"
       />
  </a>
  <figcaption>Screenshot of the Stich user interface where the frontend was designed</figcaption>
</figure>
<div class="lightbox" id="img-6c503a0a71d9422592d1787fcc85726b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/comic-trip/stitch-ui.jpg"
    alt="Screenshot of the Stich user interface where the frontend was designed"
     />
  <div class="lightbox-caption">Screenshot of the Stich user interface where the frontend was designed</div>
</div>
</p>
<p>The frontend interacts with the backend via a simple REST API.
Images are sent as <code>multipart/form-data</code>, and the backend returns a comprehensive JSON response, after reading the metadata from Firestore.
The frontend then dynamically renders the &ldquo;Comic Strip&rdquo; view, pulling the comic-styled illustrations directly from the GCS bucket URLs.</p>
<h2 id="whats-next">What&rsquo;s next?</h2>
<p>The Comic Trip app shows that you can build sophisticated, multi-agent tools without having to worry about low-level state or orchestration.
ADK for Java 1.0 handles the plumbing, so you can focus on defining how your agents behave and what tools they use.</p>
<p>If you&rsquo;re ready to start building:</p>
<ul>
<li>Poke around the <a href="https://github.com/glaforge/comic-trip-agent">source code</a> for Comic Trip.</li>
<li>Check out the <a href="https://www.youtube.com/watch?v=YqABMjSho_M">video</a> for a deep dive into ADK 1.0 features.</li>
<li>Read the <a href="https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/">official announcement</a> for more context.</li>
<li>Grab the <a href="https://github.com/glaforge/adk-java-maven-template">Maven template</a> to bootstrap your own agent.</li>
</ul>
<p>Happy building! &#x1f916;</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Generating music with Lyria 3 and the Gemini Interactions Java SDK</title><link>https://glaforge.dev/posts/2026/03/25/generating-music-with-lyria-3-and-the-gemini-interactions-java-sdk/</link><pubDate>Wed, 25 Mar 2026 23:33:32 +0100</pubDate><guid>https://glaforge.dev/posts/2026/03/25/generating-music-with-lyria-3-and-the-gemini-interactions-java-sdk/</guid><description>&lt;p>Generative AI isn&amp;rsquo;t just about text or images
(with &lt;a href="https://glaforge.dev/tags/nano-banana/">Nano Banana&lt;/a>) but it&amp;rsquo;s also great at generating videos
(with &lt;a href="https://glaforge.dev/posts/2025/09/10/generating-videos-in-java-with-veo3/">Veo 3&lt;/a>).
And now with the recently &lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/lyria-3-developers/">released&lt;/a>
&lt;strong>Lyria 3&lt;/strong> model from DeepMind, you can create some engaging and creative music with lyrics (generated, or your own)
or invent a calming instrumental track to loop in the background of your online TikTok or YouTube Shorts.&lt;/p>
&lt;p>And of course, if you&amp;rsquo;re a Java developer like me, &lt;strong>you can do all that in Java&lt;/strong>!&lt;/p></description><content:encoded>
<![CDATA[<p>Generative AI isn&rsquo;t just about text or images
(with <a href="https://glaforge.dev/tags/nano-banana/">Nano Banana</a>) but it&rsquo;s also great at generating videos
(with <a href="https://glaforge.dev/posts/2025/09/10/generating-videos-in-java-with-veo3/">Veo 3</a>).
And now with the recently <a href="https://blog.google/innovation-and-ai/technology/developers-tools/lyria-3-developers/">released</a>
<strong>Lyria 3</strong> model from DeepMind, you can create some engaging and creative music with lyrics (generated, or your own)
or invent a calming instrumental track to loop in the background of your online TikTok or YouTube Shorts.</p>
<p>And of course, if you&rsquo;re a Java developer like me, <strong>you can do all that in Java</strong>!</p>
<p>In this article, we&rsquo;ll learn how to create our own songs and clips with the <strong>Lyria 3</strong> model, in Java,
using my <a href="https://github.com/glaforge/gemini-interactions-api-sdk">Gemini Interactions API Java SDK</a>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition idea">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Idea</span>
      </div>
      <div class="admonition-content">
        <p>The examples in this article are inspired by this
<a href="https://colab.research.google.com/github/google-gemini/cookbook/blob/main/quickstarts/Get_started_Lyria.ipynb">Colab Notebook</a>
in Python. Don&rsquo;t hesitate to check it out.
And if you want to learn more, this <a href="https://blog.google/innovation-and-ai/technology/developers-tools/lyria-3-developers/">article</a>
shows some fun app integration ideas, like an alarm clock waking you up with a different song every morning!</p>
      </div>
    </div>
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>To run those examples, you&rsquo;ll need to <a href="https://aistudio.google.com/api-keys">get a Gemini API key</a>
in <strong>Google AI Studio</strong>.
You&rsquo;ll then be able to instantiate a Gemini Interactions API client as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>GeminiInteractionsClient<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GeminiInteractionsClient.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">       </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">       </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div>
      </div>
    </div><h2 id="clip-vs-pro-the-models">Clip vs. Pro: The Models</h2>
<p>Lyria 3 comes in two primary flavors:</p>
<ul>
<li><strong><code>lyria-3-clip-preview</code></strong> — Perfect for generating short clips (<strong>30 second</strong> long), snippets, or quick iterations for sound effects, choruses, and jingles.</li>
<li><strong><code>lyria-3-pro-preview</code></strong> — Fully capable of generating long, structurally cohesive, full-length songs (up to <strong>3 minutes</strong>).</li>
</ul>
<p>Here&rsquo;s an example of firing off a request for a full-length song using the SDK:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;models/lyria-3-pro-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Write a full length epic power metal song
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        about a brave knight fighting a dragon.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        It should have a guitar solo.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">AUDIO</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">TEXT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><figure class="audio">
<audio controls preload="metadata"  >
<source src="/mp3/lyria-full-knight-dragon.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</figure>
<p>In addition to the MP3, Lyria also generated the following lyrics:

<details>
  <summary>Click to read the full lyrics</summary>
  <pre tabindex="0"><code>[[A0]]
[[B1]]
[16.0:] Cold mountain peaks in the morning haze,
[:] The knight rides forth through the silver maze,
[:] With steel in hand and a heart of fire,
[:] To face the beast and the burning pyre.
[:] Through ancient gates where the shadows sleep,
[:] He finds the path to the valley deep,
[:] No fear of death in his iron soul,
[:] He seeks the fire and the final goal.
[[C2]]
[48.0:] Upon the wind comes the dragon’s breath!
[:] A storm of flame and a dance of death!
[:] Oh, carry the flame on the steel of the knight!
[:] Into the dragon, into the light!
[:] The world will tremble as titans collide!
[:] Nowhere for the ancient beast to hide!
[:] Into the light!
[[B3]]
[80.0:] The claws of iron and teeth of obsidian,
[:] A mountain of scales in the dark stygian,
[:] The sword strikes home but the sparks do fly,
[:] Underneath the heavy sulfur sky.
[:] A roar that echoes through the mountain hall,
[:] The knight stands steady, he will not fall,
[:] Through smoke and cinders the legend grows,
[:] He strikes the heart where the furnace glows!
[[C4]]
[112.0:] Upon the wind comes the dragon’s breath!
[:] A storm of flame and a dance of death!
[:] Oh, carry the flame on the steel of the knight!
[:] Into the dragon, into the light!
[:] The world will tremble as titans collide!
[:] Nowhere for the ancient beast to hide!
[:] Into the light!
[[A5]]
[[B6]]
[160.0:] The wings are broken, the fire is out,
[:] The knight is standing amidst the doubt,
[:] A savior&#39;s light in the dark of the cave,
[:] To the halls of legend, he’s wise and brave.
[[D7]]
[176.0:] The legend remains. Forevermore!
[:] (Forevermore!)
</code></pre></details>
</p>
<h2 id="mp3-decoding-and-dual-modalities">MP3 Decoding and Dual Modalities</h2>
<p>One of the coolest features of the Interactions API is the ability to request multiple <strong>Response Modalities</strong>.
Notice the <code>responseModalities</code> parameter in the code snippet above?
By requesting both <code>AUDIO</code> and <code>TEXT</code>, the API will return:</p>
<ol>
<li><strong>Text</strong>: The actual lyrics generated and the structural breakdown of the song.</li>
<li><strong>Audio</strong>: The music itself natively encoded as an <strong>MP3 file</strong>.</li>
</ol>
<p>Because of the API&rsquo;s MP3 formatting return type, you don&rsquo;t need to do any complex WAV header manipulation or PCM decoding.
You can safely extract the returned bytes from the payload and push them directly onto disks as an <code>.mp3</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>interaction.<span style="color:#4070a0">outputs</span>().<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">filter</span>(output<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>output<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>AudioContent)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">map</span>(output<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>(AudioContent)<span style="color:#bbb"> </span>output)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">findFirst</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">ifPresent</span>(audio<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Files.<span style="color:#4070a0">write</span>(Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;song.mp3&#34;</span>),<span style="color:#bbb"> </span>audio.<span style="color:#4070a0">data</span>()));<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="prompt-constraints">Prompt Constraints</h2>
<p>The Lyria 3 model is highly receptive to prompting constraints.
Depending on what you pass into the text input, here are four major ways to dictate the song output:</p>
<h3 id="1-give-it-lyrics">1. Give it Lyrics</h3>
<p>You don&rsquo;t have to rely on the model inventing lyrics.
If you have written your own song, you can literally paste the lyrics into the prompt:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;models/lyria-3-clip-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        An uplifting song with guitar riffs about nano banana.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        The lyrics should be:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          Yellow peel, a tiny sweet,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          The Nano Banana, a tropical treat.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          But wait—it hums, it starts to create,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          Switching into AI mode...
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">AUDIO</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">TEXT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><figure class="audio">
<audio controls preload="metadata"  >
<source src="/mp3/lyria-lyrics-banana.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</figure>
<h3 id="2-control-the-structure">2. Control the Structure</h3>
<p>You can instruct the model on song composition layout by using bracketed metadata
such as <code>[Intro]</code>, <code>[Verse]</code>, <code>[Chorus]</code>, and <code>[Outro]</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;models/lyria-3-clip-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        [Intro] Calm piano music setting a sunset scene on the beach
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        [Verse] Epic rock ballad as the storm rages.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        [Outro] Opera with choir as the sun reappears
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                again through the black clouds.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">AUDIO</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">TEXT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><figure class="audio">
<audio controls preload="metadata"  >
<source src="/mp3/lyria-structured-storm.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</figure>
<h3 id="3-instrumental-only">3. Instrumental Only</h3>
<p>If you aren&rsquo;t looking for lyrics or vocals, simply instruct the model that the track should be instrumental.
It excels at generating ambient background loops!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;models/lyria-3-clip-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Create a looping meditation music that feels like the wind.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Instrumental only.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">AUDIO</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><figure class="audio">
<audio controls preload="metadata"  >
<source src="/mp3/lyria-instrumental-wind.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</figure>
<h3 id="4-give-it-a-picture-for-inspiration">4. Give it a Picture for Inspiration!</h3>
<p>Since Lyria 3 is a <strong>multimodal model</strong>, not only can it accept a prompt in input,
but you can also pass images to drive its generative inspiration:</p>
<p><figure>
  <a href="#img-8fb1ac3676e6ab7c2826694804db3fee">
    <img src="https://storage.googleapis.com/generativeai-downloads/images/groceries.jpeg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8fb1ac3676e6ab7c2826694804db3fee">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/generativeai-downloads/images/groceries.jpeg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// picture of a groceries list which will drive the lyrics</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>imageBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>URI.<span style="color:#4070a0">create</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;https://storage.googleapis.com/generativeai-downloads/images/groceries.jpeg&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toURL</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">openStream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">readAllBytes</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;models/lyria-3-clip-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextContent(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            An epic song with opera voices about this quest.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Deep synths and a speeding up tempo.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ImageContent(imageBytes,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/jpeg&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">AUDIO</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">TEXT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><figure class="audio">
<audio controls preload="metadata"  >
<source src="/mp3/lyria-image-groceries.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</figure>
<h2 id="wrap-up">Wrap Up</h2>
<p>Adding music generation to Java applications or AI agents is easier than ever with the Interactions API and Lyria 3.
I highly recommend taking a look at the newly added
<a href="https://github.com/glaforge/gemini-interactions-api-sdk?tab=readme-ov-file#lyria-music-generation">test cases</a>
over in the <code>LyriaTest.java</code> class within the SDK repository to see the full setup in action.</p>
<p>Happy prompting &amp; rocking! 🎸</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Extracting JSON from LLM chatter with JsonSpotter</title><link>https://glaforge.dev/posts/2026/03/22/extracting-json-from-llm-chatter-with-jsonspotter/</link><pubDate>Sun, 22 Mar 2026 17:53:56 +0100</pubDate><guid>https://glaforge.dev/posts/2026/03/22/extracting-json-from-llm-chatter-with-jsonspotter/</guid><description>&lt;p>LLMs are great at generating structured data, in particularly those which support a strict &lt;em>JSON output mode&lt;/em> (sometimes also called &lt;em>structured decoding&lt;/em>), but sometimes they give you a bit more than the requested JSON. You get a Markdown code block wrapped in &lt;em>&amp;ldquo;Here&amp;rsquo;s the data you asked for:&amp;rdquo;&lt;/em> and &lt;em>&amp;ldquo;Hope this helps!&amp;rdquo;&lt;/em>. If you&amp;rsquo;re lucky, the JSON is valid. If you&amp;rsquo;re not, it has trailing commas or comments that break standard parsers.&lt;/p></description><content:encoded>
<![CDATA[<p>LLMs are great at generating structured data, in particularly those which support a strict <em>JSON output mode</em> (sometimes also called <em>structured decoding</em>), but sometimes they give you a bit more than the requested JSON. You get a Markdown code block wrapped in <em>&ldquo;Here&rsquo;s the data you asked for:&rdquo;</em> and  <em>&ldquo;Hope this helps!&rdquo;</em>. If you&rsquo;re lucky, the JSON is valid. If you&rsquo;re not, it has trailing commas or comments that break standard parsers.</p>
<p>I wrote <a href="https://github.com/glaforge/jsonspotter"><strong>JsonSpotter</strong></a> to handle this. It’s a small <strong>Java</strong> library that finds and extracts JSON-like structures from any text, even if the JSON itself is a bit messy.
Then, you can use a lenient JSON parser like <a href="https://github.com/FasterXML/jackson">Jackson 3</a> to parse the extracted JSON to <strong>work with proper type-safe Java objects</strong>, instead of text or maps of lists of maps of&hellip; more basic types.</p>
<h2 id="why-not-just-use-regex">Why not just use Regex?</h2>
<p>You could try <code>indexOf(&quot;{&quot;)</code> or a regular expression, but those break quickly. Nested objects, extra braces in the conversational text, or complex arrays make string manipulation a nightmare.</p>
<p><code>JsonSpotter</code> uses a recursive descent parser. It actually understands the structure it&rsquo;s looking for. It doesn&rsquo;t just find brackets; it validates the object or array boundaries structurally as it scans. By doing so, it can accurately find the <strong>longest well-balanced JSON-like structure</strong> in the text, ensuring that nested objects are handled correctly and that it doesn&rsquo;t get tripped up by random braces in the conversational text.</p>
<h2 id="handling-lenient-json">Handling &ldquo;lenient&rdquo; JSON</h2>
<p>Sometimes LLMs output what I call &ldquo;human-friendly&rdquo; JSON. They don&rsquo;t always output strict and valid JSON. They can add comments to explain fields, add an ellipsis to omit parts of the content, or leave trailing commas. By default, standard libraries like Jackson or Gson will throw an error immediately when they see a <code>// comment</code> or a trailing <code>,</code>.</p>
<p><code>JsonSpotter</code> is built to be <strong>lenient</strong> during extraction. It recognizes:</p>
<ul>
<li>Single and multi-line comments (<code>//</code>, <code>/* */</code>, <code>#</code>)</li>
<li>Unquoted keys and single-quoted strings</li>
<li>Trailing commas</li>
<li>Non-standard numbers like <code>.5</code> or <code>NaN</code></li>
</ul>
<p>Once <code>JsonSpotter</code> extracts the raw string, you can pass it to a proper JSON parser configured for <em>leniency</em> (i.e. being tolerant to malformed JSON content).</p>
<h2 id="a-quick-example">A quick example</h2>
<p>First, extract the JSON from your LLM response:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>rawText<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;...&#34;</span>;<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Text returned by your LLM</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>jsonString<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>JsonSpotter.<span style="color:#4070a0">extractJson</span>(rawText);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, parse it with something like Jackson 3 (which has great support for lenient features):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">tools.jackson.databind.json.JsonMapper</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">tools.jackson.core.json.JsonReadFeature</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>JsonMapper<span style="color:#bbb"> </span>mapper<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>JsonMapper.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">enable</span>(JsonReadFeature.<span style="color:#4070a0">ALLOW_JAVA_COMMENTS</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">enable</span>(JsonReadFeature.<span style="color:#4070a0">ALLOW_TRAILING_COMMA</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">enable</span>(JsonReadFeature.<span style="color:#4070a0">ALLOW_SINGLE_QUOTES</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">enable</span>(JsonReadFeature.<span style="color:#4070a0">ALLOW_UNQUOTED_PROPERTY_NAMES</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>MyClass<span style="color:#bbb"> </span>myObj<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">  </span>mapper.<span style="color:#4070a0">readValue</span>(jsonString,<span style="color:#bbb"> </span>MyClass.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// or</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>JsonNode<span style="color:#bbb"> </span>node<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>mapper.<span style="color:#4070a0">readTree</span>(jsonString);<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="get-it">Get it</h2>
<p><code>JsonSpotter</code> is dependency-free and available on Maven Central.</p>
<p>Add the dependency to your <code>pom.xml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.github.glaforge.jsonspotter<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>jsonspotter<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.1.2<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>Or your <code>build.gradle</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    implementation<span style="color:#666">(</span><span style="color:#4070a0">&#34;io.github.glaforge.jsonspotter:jsonspotter:0.1.2&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Give it a try and let me know what you think on <a href="https://github.com/glaforge/jsonspotter">GitHub</a> if you find it useful.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Fixing AI Slop with a Skill in Gemini CLI</title><link>https://glaforge.dev/posts/2026/03/08/fixing-ai-slop-with-a-skill-in-gemini-cli/</link><pubDate>Sun, 08 Mar 2026 21:29:38 +0100</pubDate><guid>https://glaforge.dev/posts/2026/03/08/fixing-ai-slop-with-a-skill-in-gemini-cli/</guid><description>&lt;p>We all recognize AI writing when we see it. As language models get used for everything, their specific tics are everywhere.
People call this &lt;strong>&amp;ldquo;AI slop&amp;rdquo;&lt;/strong>. The grammar is fine, but the text is boring, repetitive, and lacks any real voice.&lt;/p>
&lt;p>A site called &lt;a href="https://tropes.fyi/">tropes.fyi&lt;/a> tracks these patterns. It lists the exact words and structures that give AI away.
You&amp;rsquo;ll see callouts for overused adverbs like &lt;em>&amp;ldquo;deeply&amp;rdquo;&lt;/em> and &lt;em>&amp;ldquo;arguably,&amp;rdquo;&lt;/em> the dreaded &lt;em>&amp;ldquo;delve&amp;rdquo;&lt;/em> family, and structural crutches like &lt;em>&amp;ldquo;It&amp;rsquo;s not X, it&amp;rsquo;s Y.&amp;rdquo;&lt;/em>&lt;/p></description><content:encoded>
<![CDATA[<p>We all recognize AI writing when we see it. As language models get used for everything, their specific tics are everywhere.
People call this <strong>&ldquo;AI slop&rdquo;</strong>. The grammar is fine, but the text is boring, repetitive, and lacks any real voice.</p>
<p>A site called <a href="https://tropes.fyi/">tropes.fyi</a> tracks these patterns. It lists the exact words and structures that give AI away.
You&rsquo;ll see callouts for overused adverbs like <em>&ldquo;deeply&rdquo;</em> and <em>&ldquo;arguably,&rdquo;</em> the dreaded <em>&ldquo;delve&rdquo;</em> family, and structural crutches like <em>&ldquo;It&rsquo;s not X, it&rsquo;s Y.&rdquo;</em></p>
<p>I wanted a way to automatically fix these issues in my own generated text.
So, I built a <code>deslopify</code> skill for <a href="https://geminicli.com/">Gemini CLI</a>.</p>
<h2 id="how-i-built-it">How I built it</h2>
<p>Gemini CLI has a built-in <a href="https://geminicli.com/docs/cli/creating-skills/"><code>skill-creator</code></a> that handles the boilerplate.
I just asked the CLI to create a new skill, and it generated the folder structure and a <code>SKILL.md</code> file.
It took as reference the Markdown file shared by <a href="https://tropes.fyi/">tropes.fyi</a> with all the typical elements of AI sloppiness.</p>
<p>The <code>SKILL.md</code> file tells the agent how to behave.
I wrote a short workflow instructing the agent to read the user&rsquo;s text, cross-reference it with the anti-patterns from <a href="https://tropes.fyi/">tropes.fyi</a>, and rewrite it.
The goal is just to make the text sound like a normal person wrote it.</p>
<p>Here&rsquo;s what the skill generated by Gemini CLI looks like:</p>
<p><figure>
  <a href="#img-2569003cacf359aaaf2dfc52174a5c0d">
    <img src="/img/gemini-cli/deslopify-skill.jpg"
      alt="deslopify generated skill"
       />
  </a>
  <figcaption>deslopify generated skill</figcaption>
</figure>
<div class="lightbox" id="img-2569003cacf359aaaf2dfc52174a5c0d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/deslopify-skill.jpg"
    alt="deslopify generated skill"
     />
  <div class="lightbox-caption">deslopify generated skill</div>
</div>
</p>
<p>The structure of the skill is just this <code>SKILL.md</code> file, and a <code>references/</code> directory containing the style guide:</p>
<pre tabindex="0"><code>deslopify/
├── SKILL.md
└── references/
      └── style_guide.md
</code></pre><p>I didn&rsquo;t even have to modify the generated <code>SKILL.md</code> as it did the job perfectly, and was clear out of the box.</p>
<h2 id="trying-it-out">Trying it out</h2>
<p>The code is up on GitHub: <a href="https://github.com/glaforge/deslopify">glaforge/deslopify</a>.</p>
<p>You can install it directly from the repo in Gemini CLI:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gemini skills install https://github.com/glaforge/deslopify
</span></span></code></pre></div><p><figure>
  <a href="#img-f9bd144ff7fb97c7f65cf7bc41d4670d">
    <img src="/img/gemini-cli/install-deslopify-cli-skill.jpg"
      alt="Installing the deslopify skill within Gemini CLI"
       />
  </a>
  <figcaption>Installing the deslopify skill within Gemini CLI</figcaption>
</figure>
<div class="lightbox" id="img-f9bd144ff7fb97c7f65cf7bc41d4670d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/install-deslopify-cli-skill.jpg"
    alt="Installing the deslopify skill within Gemini CLI"
     />
  <div class="lightbox-caption">Installing the deslopify skill within Gemini CLI</div>
</div>
</p>
<p>Then reload your interactive session:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>/skills reload
</span></span></code></pre></div>
            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Information</span>
      </div>
      <div class="admonition-content">
        <p>Because the skill format is just a directory with a <code>SKILL.md</code> file and some assets, you can actually use it in any AI agent that supports the standard.
Be sure to check the syntax and command to install it in your favorite agent.
And feel free to read more about <a href="https://agentskills.io/home">agent skills</a> to understand their structure.</p>
      </div>
    </div><p>Once it&rsquo;s loaded, you can ask Gemini CLI (or your preferred agent tool) to clean up your text:</p>
<ul>
<li><code>&quot;Deslopify this article: [URL]&quot;</code></li>
<li><code>&quot;Naturalize this draft: [paste text]&quot;</code></li>
<li><code>&quot;Remove the AI slop from my README.md file.&quot;</code></li>
</ul>
<p>The agent will strip out the pompous phrasing and weird structural tics, leaving you with something far more readable.</p>

    <div class="admonition question">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM169.8 165.3c7.9-22.3 29.1-37.3 52.8-37.3l58.3 0c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24l0-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1l-58.3 0c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Is this text generated?</span>
      </div>
      <div class="admonition-content">
        <p>If you were wondering, yes, I generated a good chunk of this blog post from my session in Gemini CLI when creating the <code>deslopify</code> skill!
I went through each and every sentence and made some light edits or sometimes added a paragraph or extra clarification sentence.
I also added all the screenshots, added missing links.
But the skill worked, and for quickly sharing details about a little project like this, this skill came in handy to avoid the <em>blank page</em> syndrome.
Hopefully, you won&rsquo;t feel bored reading it!</p>
      </div>
    </div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Easily Build a Local MCP Server in Java with a Skill inside Gemini CLI</title><link>https://glaforge.dev/posts/2026/02/21/easily-build-a-local-mcp-server-in-java-with-a-skill-in-gemini-cli/</link><pubDate>Sat, 21 Feb 2026 22:57:44 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/21/easily-build-a-local-mcp-server-in-java-with-a-skill-in-gemini-cli/</guid><description>&lt;p>Recently, I&amp;rsquo;ve been exploring the &lt;strong>Model Context Protocol&lt;/strong> (MCP)
and how to easily create custom servers to extend the capabilities of AI assistants like
&lt;a href="https://geminicli.com">Gemini CLI&lt;/a> which I use daily.&lt;/p>
&lt;p>I wanted a way to build these servers &lt;strong>in Java&lt;/strong> without the heavy boilerplate of a traditional Maven or Gradle project,
or with a complex framework.
The solution? Combining &lt;a href="https://jbang.dev">JBang&lt;/a>, &lt;a href="https://docs.langchain4j.dev">LangChain4j&lt;/a>,
and&amp;hellip; &amp;#x1f941;&amp;hellip; a custom &lt;a href="https://geminicli.com/docs/cli/skills/">Gemini CLI skill&lt;/a>!&lt;/p>
&lt;p>In this post, I&amp;rsquo;ll walk you through how I streamlined the creation of MCP STDIO servers,
by &lt;strong>creating an agent &lt;code>SKILL.md&lt;/code>&lt;/strong> to replicate what I had learned in my previous article.&lt;/p></description><content:encoded>
<![CDATA[<p>Recently, I&rsquo;ve been exploring the <strong>Model Context Protocol</strong> (MCP)
and how to easily create custom servers to extend the capabilities of AI assistants like
<a href="https://geminicli.com">Gemini CLI</a> which I use daily.</p>
<p>I wanted a way to build these servers <strong>in Java</strong> without the heavy boilerplate of a traditional Maven or Gradle project,
or with a complex framework.
The solution? Combining <a href="https://jbang.dev">JBang</a>, <a href="https://docs.langchain4j.dev">LangChain4j</a>,
and&hellip; &#x1f941;&hellip; a custom <a href="https://geminicli.com/docs/cli/skills/">Gemini CLI skill</a>!</p>
<p>In this post, I&rsquo;ll walk you through how I streamlined the creation of MCP STDIO servers,
by <strong>creating an agent <code>SKILL.md</code></strong> to replicate what I had learned in my previous article.</p>
<h2 id="the-recap-jbang-and-langchain4j">The Recap: JBang and LangChain4j</h2>
<p>In that
<a href="https://glaforge.dev/posts/2026/02/11/zero-boilerplate-java-stdio-mcp-servers-with-langchain4j-and-jbang/">article</a>
I wrote recently, <strong>JBang</strong> was perfect for <strong>writing and running self-contained Java scripts</strong>.
It automatically handles dependencies and JVM execution, making it perfect for lightweight MCP servers.
No need for a directory structure, for build files, or pre-compilation.</p>
<p><strong>LangChain4j</strong>&rsquo;s recent
<a href="https://github.com/langchain4j/langchain4j/releases/tag/1.11.0">release</a>
provided the <code>langchain4j-community-mcp-server</code>
<a href="https://docs.langchain4j.dev/tutorials/mcp-stdio-server/#start-the-stdio-server">module</a>,
which allows you to create STDIO MCP servers, without the need for a server framework.
By simply annotating a method of a Java class with <code>@Tool</code>, we can expose some useful tool to an LLM.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Beware</span>
      </div>
      <div class="admonition-content">
        <p>The critical requirement for an MCP STDIO server is ensuring that JSON-RPC communication over <code>System.out</code> remains uncorrupted.
This means all logging <em>must</em> be redirected to <code>System.err</code>.</p>
      </div>
    </div><p>But to create and install a new MCP server, I had to do a fair bit of copy and paste, and a bit of scaffolding.
That&rsquo;s how I came up with the &#x1f4a1; idea of <strong>creating an agent skill to simplify this task</strong>!</p>
<h2 id="step-1--automating-with-a-skill-for-gemini-cli">Step 1 — Automating with a Skill for Gemini CLI</h2>
<p>Instead of writing the boilerplate manually every time, I first created a custom Gemini CLI skill (<code>jbang-mcp-server.skill</code>).
To do this efficiently, I leveraged Gemini CLI&rsquo;s own <a href="https://geminicli.com/docs/cli/creating-skills/"><code>skill-creator</code> skill</a>,
which is designed to bootstrap new capabilities for the agent.</p>
<p><figure>
  <a href="#img-8d8513216c90f3464cd35eb92863e9c2">
    <img src="/img/mcp/skill/request-skill-creation.jpg"
      alt="Requesting a SKILL.md Creation by Gemini CLI&rsquo;s Skill Creator"
       />
  </a>
  <figcaption>Requesting a SKILL.md Creation by Gemini CLI&rsquo;s Skill Creator</figcaption>
</figure>
<div class="lightbox" id="img-8d8513216c90f3464cd35eb92863e9c2">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/skill/request-skill-creation.jpg"
    alt="Requesting a SKILL.md Creation by Gemini CLI&rsquo;s Skill Creator"
     />
  <div class="lightbox-caption">Requesting a SKILL.md Creation by Gemini CLI&rsquo;s Skill Creator</div>
</div>
</p>
<p>I fed the skill creator the whole article mentioned above,
and it produced the following <code>SKILL.md</code> file to act as a specialized &ldquo;generator&rdquo;
for automating the repetitive parts of building and installing Java-based MCP servers:</p>
<details>
<summary>Click to view the generated SKILL.md</summary>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>name: jbang-mcp-server
</span></span><span style="display:flex;"><span>description: Scaffolds and installs zero boilerplate Java-based MCP STDIO servers using JBang and LangChain4j for Gemini CLI. Use this to quickly bootstrap an MCP server from scratch.
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"># JBang LangChain4j MCP Server Creator
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>This skill helps quickly scaffold a new Java-based MCP STDIO server using JBang and LangChain4j, and installs it into Gemini CLI&#39;s <span style="color:#4070a0">`settings.json`</span>.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold">## Process
</span></span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">1.</span>  <span style="font-weight:bold">**Ask User for Details:**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Desired file name (e.g., <span style="color:#4070a0">`McpToolServer.java`</span>) and path to save it.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   The name of the server to register in <span style="color:#4070a0">`~/.gemini/settings.json`</span> (e.g., <span style="color:#4070a0">`java-calc`</span>).
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   (Optional) High-level description of the tools they want to add initially.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">2.</span>  <span style="font-weight:bold">**Scaffold the Server:**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Read the template file located at <span style="color:#4070a0">`assets/McpServerTemplate.java`</span> using the <span style="color:#4070a0">`read_file`</span> tool. Note that the path to <span style="color:#4070a0">`assets/McpServerTemplate.java`</span> needs to be resolved relative to the skill directory or read from the skill&#39;s bundled assets. As an alternative if the absolute path is unknown, directly write out the template contents described below.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Replace <span style="color:#4070a0">`{SERVER_CLASS_NAME}`</span> with the base name of the requested Java file (e.g., <span style="color:#4070a0">`McpToolServer`</span> if file is <span style="color:#4070a0">`McpToolServer.java`</span>).
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Replace <span style="color:#4070a0">`{TOOL_CLASS_NAME}`</span> with a related name (e.g., <span style="color:#4070a0">`MyTools`</span>).
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   (Optional) Modify the <span style="color:#4070a0">`@Tool`</span> annotated methods to reflect the user&#39;s requirements.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Write the finalized content to the user&#39;s requested path using the <span style="color:#4070a0">`write_file`</span> tool.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Make the file executable using <span style="color:#4070a0">`chmod +x &lt;path_to_file&gt;`</span> via <span style="color:#4070a0">`run_shell_command`</span>.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">3.</span>  <span style="font-weight:bold">**Verify the Server:**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Run jbang build &lt;<span style="color:#062873;font-weight:bold">path_to_file</span>&gt; using the run_shell_command tool to check for any compilation errors.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   If there are compilation errors, use the <span style="color:#4070a0">`replace_tool`</span> to fix them.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Repeat the compilation check until successful.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">4.</span>  <span style="font-weight:bold">**Install the Server in Gemini CLI:**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Read <span style="color:#4070a0">`~/.gemini/settings.json`</span>.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   Use the <span style="color:#4070a0">`replace`</span> tool or jq via <span style="color:#4070a0">`run_shell_command`</span> to inject a new entry under <span style="color:#4070a0">`mcpServers`</span>.
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">*</span>   The new entry should look like this:
</span></span><span style="display:flex;"><span>        ``<span style="color:#4070a0">`json
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;{server_name}&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          &#34;command&#34;: &#34;jbang&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          &#34;args&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;run&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;--quiet&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;{absolute_path_to_java_file}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          ]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        `</span>`<span style="color:#4070a0">`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    *   Inform the user that the server has been created and configured, and remind them that Gemini CLI automatically reloads configurations.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">## Template Backup
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">If `</span>assets/McpServerTemplate.java<span style="color:#4070a0">` cannot be read, use this template:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">`</span>``java
</span></span><span style="display:flex;"><span>///usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?
</span></span><span style="display:flex;"><span>//DEPS dev.langchain4j:langchain4j-core:1.11.0
</span></span><span style="display:flex;"><span>//DEPS dev.langchain4j:langchain4j-community-mcp-server:1.11.0-beta19
</span></span><span style="display:flex;"><span>//DEPS org.slf4j:slf4j-simple:2.0.17
</span></span><span style="display:flex;"><span>//JAVA 21
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>import dev.langchain4j.agent.tool.Tool;
</span></span><span style="display:flex;"><span>import dev.langchain4j.community.mcp.server.McpServer;
</span></span><span style="display:flex;"><span>import dev.langchain4j.community.mcp.server.transport.StdioMcpServerTransport;
</span></span><span style="display:flex;"><span>import org.slf4j.Logger;
</span></span><span style="display:flex;"><span>import org.slf4j.LoggerFactory;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>import java.util.List;
</span></span><span style="display:flex;"><span>import java.util.concurrent.CountDownLatch;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>public class {SERVER_CLASS_NAME} {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    static {
</span></span><span style="display:flex;"><span>        // Configure SLF4J Simple Logger to write to System.err
</span></span><span style="display:flex;"><span>        // This is crucial for MCP servers over STDIO to avoid polluting stdout
</span></span><span style="display:flex;"><span>        System.setProperty(&#34;org.slf4j.simpleLogger.logFile&#34;, &#34;System.err&#34;);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    private static final Logger log = LoggerFactory.getLogger({SERVER_CLASS_NAME}.class);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    public static void main(String[] args) throws Exception {
</span></span><span style="display:flex;"><span>        log.info(&#34;Starting LangChain4j MCP Server...&#34;);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        // Instantiate tools
</span></span><span style="display:flex;"><span>        {TOOL_CLASS_NAME} tools = new {TOOL_CLASS_NAME}();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        // Create Server
</span></span><span style="display:flex;"><span>        McpServer server = new McpServer(List.of(tools));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        // Start Transport
</span></span><span style="display:flex;"><span>        StdioMcpServerTransport transport = new StdioMcpServerTransport(server);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        log.info(&#34;MCP Server started successfully on STDIO.&#34;);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        // Keep Alive
</span></span><span style="display:flex;"><span>        new CountDownLatch(1).await();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    // --- Tool Definition ---
</span></span><span style="display:flex;"><span>    public static class {TOOL_CLASS_NAME} {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#d55537;font-weight:bold">@Tool</span>(&#34;Description of your tool&#34;)
</span></span><span style="display:flex;"><span>        public String sampleTool(String input) {
</span></span><span style="display:flex;"><span>            log.info(&#34;Called sampleTool with {}&#34;, input);
</span></span><span style="display:flex;"><span>            return &#34;Processed: &#34; + input;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="key-rules">Key Rules</h2>
<ul>
<li><strong>Logging:</strong> JBang STDIO servers MUST write all logs to <code>System.err</code> to avoid polluting the JSON-RPC standard output stream. This is already handled in the template via <code>System.setProperty(&quot;org.slf4j.simpleLogger.logFile&quot;, &quot;System.err&quot;);</code> but ensure this is maintained if modifying the file structure.</li>
<li><strong>Dependencies:</strong> The template relies on LangChain4j and slf4j-simple. Do not remove the <code>//DEPS</code> directives at the top of the template.</li>
</ul>
</details>
<p>Along the way, Gemini CLI asked me a few questions, like how to name the skill, the Java class, etc.
And of course, it also asked me for permission to create that skill:</p>
<p><figure>
  <a href="#img-b6e94b6ab8dcaf9de22596584c1cf698">
    <img src="/img/mcp/skill/activate-skill.jpg"
      alt="Gemini CLI asked for Permission to Create a Skill"
       />
  </a>
  <figcaption>Gemini CLI asked for Permission to Create a Skill</figcaption>
</figure>
<div class="lightbox" id="img-b6e94b6ab8dcaf9de22596584c1cf698">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/skill/activate-skill.jpg"
    alt="Gemini CLI asked for Permission to Create a Skill"
     />
  <div class="lightbox-caption">Gemini CLI asked for Permission to Create a Skill</div>
</div>
</p>
<p>Once installed, I could check that the skill was available in my Gemini CLI session:</p>
<p><figure>
  <a href="#img-cd8744d027eb3e31c005678269681791">
    <img src="/img/mcp/skill/skills-list.jpg"
      alt="Gemini CLI Skills List"
       />
  </a>
  <figcaption>Gemini CLI Skills List</figcaption>
</figure>
<div class="lightbox" id="img-cd8744d027eb3e31c005678269681791">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/skill/skills-list.jpg"
    alt="Gemini CLI Skills List"
     />
  <div class="lightbox-caption">Gemini CLI Skills List</div>
</div>
</p>
<p>Here is how the skill works and why it&rsquo;s useful:</p>
<h3 id="zero-boilerplate-scaffolding">Zero-Boilerplate Scaffolding</h3>
<p>When triggered, the skill creates a standalone Java file that is immediately ready to run as a script.
It automatically includes:</p>
<ul>
<li><strong>JBang Directives:</strong> <code>//DEPS</code> and <code>//JAVA</code> lines so you don&rsquo;t need a <code>pom.xml</code> or <code>build.gradle</code>.</li>
<li><strong>MCP Server Setup:</strong> The boilerplate code required to initialize the <code>McpServer</code> and connect it to a <code>StdioMcpServerTransport</code>.</li>
<li><strong>Critical Logging Configuration:</strong> It includes a static block that redirects all SLF4J logs to <code>System.err</code>.
This is vital for MCP STDIO servers because logging to <code>System.out</code> would corrupt the JSON-RPC messages used to talk to the AI.</li>
</ul>
<h3 id="automatic-registration">Automatic Registration</h3>
<p>One of the most tedious parts of adding an MCP server is editing the <code>~/.gemini/settings.json</code> file manually.
This skill handles that automatically:</p>
<ul>
<li>It calculates the absolute path to your new Java file.</li>
<li>It injects a new entry into the <code>mcpServers</code> section of your configuration.</li>
<li>It sets up the <code>jbang run --quiet</code> command so Gemini CLI knows exactly how to start your server.</li>
</ul>
<h3 id="rapid-tool-development">Rapid Tool Development</h3>
<p>The skill provides a template with a sample <code>@Tool</code>.
This means you can go from &ldquo;I want a new tool&rdquo; to &ldquo;I have a working tool&rdquo; in seconds
by just naming the server and then having the AI modifying the logic inside the newly generated Java class.</p>
<p>With this skill installed, bootstrapping a new MCP server takes seconds rather than minutes.</p>
<h2 id="step-2--building-the-file-tree-tool">Step 2 — Building the &ldquo;File Tree&rdquo; Tool</h2>
<p>Using our new skill, to take it for a ride, I scaffolded a server named <code>TreeMcpServer.java</code>.
My goal was to create a tool that the LLM could use to inspect the local file system structure.</p>
<p>I let Gemini CLI implement a <code>FileTreeTools</code> class with a <code>tree</code> method.
This method takes a directory path and uses Java&rsquo;s <code>java.nio.file</code> API
to recursively build a string representation of the directory tree
(limiting the depth to prevent massive outputs).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Displays a tree of the local directories and files in the specified path&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">tree</span>(String<span style="color:#bbb"> </span>pathStr)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... directory traversal logic ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="step-3--troubleshooting-the-build">Step 3 — Troubleshooting the Build</h2>
<p>I hit a small snag during development.
When I first asked Gemini CLI to show the file tree, it couldn&rsquo;t connect to the tool.
This was actually due to a compilation error.</p>
<p>To debug, I asked Gemini CLI to run a compilation check using JBang: <code>jbang build TreeMcpServer.java</code>.
This immediately highlighted the issue:
I had some unclosed string literals in the generated Java code where newline characters were literally inserted instead of escaped.</p>
<p>Using the Gemini CLI&rsquo;s <code>replace</code> tool, I quickly fixed the string literals.
But what was interesting with this issue is that I was able to <strong>ask Gemini CLI to update the <code>SKILL.md</code></strong>
to double check that the generated code compiled properly.
This allowed me to <strong>improve the skill to be more rock-solid</strong>!</p>

    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Reload skills</span>
      </div>
      <div class="admonition-content">
        <p>In Gemini CLI, if you updated a skill (for example, here, I improved the skill to handle potential compilation errors)
you can request to reload the skill with the following slash command:</p>
<pre tabindex="0"><code>/skills reload
</code></pre>
      </div>
    </div><h2 id="the-result">The Result</h2>
<p>With the compilation issues resolved, the Gemini CLI immediately recognized the newly registered <code>file-tree</code> MCP server.
When asked to &ldquo;Show me a file tree of the current directory,&rdquo; the CLI autonomously invoked our Java tool:</p>
<p><figure>
  <a href="#img-4d8e6a9fead3b3fb9da9fa41daa5231a">
    <img src="/img/mcp/skill/mcp-tool-call.jpg"
      alt="Gemini CLI MCP Tool Call Approval"
       />
  </a>
  <figcaption>Gemini CLI MCP Tool Call Approval</figcaption>
</figure>
<div class="lightbox" id="img-4d8e6a9fead3b3fb9da9fa41daa5231a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/skill/mcp-tool-call.jpg"
    alt="Gemini CLI MCP Tool Call Approval"
     />
  <div class="lightbox-caption">Gemini CLI MCP Tool Call Approval</div>
</div>
</p>
<p>&hellip;and returned a clean, formatted representation of the workspace directly in the chat:</p>
<p><figure>
  <a href="#img-1a3c4d69d8eaacb875b0bf3448b39145">
    <img src="/img/mcp/skill/mcp-tool-result.jpg"
      alt="Gemini CLI MCP Tool Result"
       />
  </a>
  <figcaption>Gemini CLI MCP Tool Result</figcaption>
</figure>
<div class="lightbox" id="img-1a3c4d69d8eaacb875b0bf3448b39145">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/skill/mcp-tool-result.jpg"
    alt="Gemini CLI MCP Tool Result"
     />
  <div class="lightbox-caption">Gemini CLI MCP Tool Result</div>
</div>
</p>
<p>This workflow — using an AI assistant to build a skill,
which in turn builds a tool that extends the assistant itself —
is a powerful demonstration of how quickly we can iterate and expand our development capabilities
using standard Java tools like <a href="https://jbang.dev">JBang</a> and <a href="https://docs.langchain4j.dev">LangChain4j</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Agent skills are a powerful way to <strong>automate boring, repetitive work</strong>.
Instead of manually scaffolding boilerplate code every time you want to create a new MCP server (or any other task),
you can delegate that task to an AI agent — and then <strong>automatically package what you learned into a reusable skill</strong>
that you can even share with others.</p>
<p>What&rsquo;s particularly interesting is how the Gemini CLI <strong>agent creation skill</strong> works as a bridge between exploration and automation.
During my interactive session with Gemini CLI, I experimented with building MCP servers, discovered the patterns,
and hit challenges that I solved on the fly.</p>
<p>Rather than keeping that knowledge locked in chat history, the agent creation skill let me capture and summarize
everything I had learned — the best practices, the gotchas, the template structure — into a single, reusable <code>SKILL.md</code> file.</p>
<p>Now, what took me a session of trial-and-error can be replicated instantly by anyone (or by future me) with a single skill invocation.</p>

    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>The Real Power of Agent Skills</span>
      </div>
      <div class="admonition-content">
        <p>They turn ad-hoc experimentation into systematic, shareable automation!</p>
<p>If you want to <strong>learn more about agent skills</strong>, be sure to check
this <a href="https://danicat.dev/posts/agent-skills-gemini-cli/">great article</a> from my colleague Daniela,
who used skills to <em>turn a repetitive task into a more automated workflow</em>.</p>
      </div>
    </div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Zero Boilerplate Java STDIO MCP Servers with LangChain4j and JBang</title><link>https://glaforge.dev/posts/2026/02/11/zero-boilerplate-java-stdio-mcp-servers-with-langchain4j-and-jbang/</link><pubDate>Wed, 11 Feb 2026 10:55:40 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/11/zero-boilerplate-java-stdio-mcp-servers-with-langchain4j-and-jbang/</guid><description>&lt;p>By now, you&amp;rsquo;re certainly all familiar with the &lt;strong>Model Context Protocol (MCP)&lt;/strong>?
It&amp;rsquo;s the standard for connecting Large Language Models (LLMs) to tools and data.
But if you look at the current ecosystem, you&amp;rsquo;ll see a lot of Python and TypeScript&amp;hellip;&lt;/p>
&lt;p>As a Java developer, you might be wondering:
&lt;em>How can I easily and quickly run my own MCP servers?&lt;/em>&lt;/p>
&lt;p>On this blog, I&amp;rsquo;ve explained how to develop MCP servers
with &lt;a href="https://glaforge.dev/tags/quarkus/">Quarkus&lt;/a> and &lt;a href="https://glaforge.dev/tags/micronaut/">Micronaut&lt;/a>.
But thanks to a recent &lt;a href="https://docs.langchain4j.dev/tutorials/mcp-stdio-server#start-the-stdio-server">community contribution&lt;/a>
to &lt;strong>&lt;a href="https://docs.langchain4j.dev">LangChain4j&lt;/a>&lt;/strong>,
and the simplicity of &lt;strong>&lt;a href="https://jbang.dev">JBang&lt;/a>&lt;/strong>,
building a local MCP server in Java is even easier and with zero boilerplate.&lt;/p></description><content:encoded>
<![CDATA[<p>By now, you&rsquo;re certainly all familiar with the <strong>Model Context Protocol (MCP)</strong>?
It&rsquo;s the standard for connecting Large Language Models (LLMs) to tools and data.
But if you look at the current ecosystem, you&rsquo;ll see a lot of Python and TypeScript&hellip;</p>
<p>As a Java developer, you might be wondering:
<em>How can I easily and quickly run my own MCP servers?</em></p>
<p>On this blog, I&rsquo;ve explained how to develop MCP servers
with <a href="https://glaforge.dev/tags/quarkus/">Quarkus</a> and <a href="https://glaforge.dev/tags/micronaut/">Micronaut</a>.
But thanks to a recent <a href="https://docs.langchain4j.dev/tutorials/mcp-stdio-server#start-the-stdio-server">community contribution</a>
to <strong><a href="https://docs.langchain4j.dev">LangChain4j</a></strong>,
and the simplicity of <strong><a href="https://jbang.dev">JBang</a></strong>,
building a local MCP server in Java is even easier and with zero boilerplate.</p>
<p>In this post, <strong>we&rsquo;ll build a standalone Java MCP server that runs over STDIO</strong>,
perfect for local integration with tools like the <a href="https://geminicli.com/">Gemini CLI</a>
or other locally running agentic tools supporting MCP servers.</p>
<hr />
<h2 id="the-stack-why-this-matters">The Stack: Why This Matters</h2>
<p>To keep things lightweight, we’re using two powerful tools:</p>
<ol>
<li><strong>LangChain4j</strong>:
The leading framework for building AI-powered Java applications.
It now includes a dedicated MCP server module for the STDIO protocol
(in addition to the existing <a href="https://docs.langchain4j.dev/tutorials/mcp">MCP client module</a>.)</li>
<li><strong>JBang</strong>:
A tool that lets you run Java files as scripts.
No <code>pom.xml</code>, no Gradle builds, just a single <code>.java</code> file with your dependencies declared right at the top.</li>
</ol>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Requirement</span>
      </div>
      <div class="admonition-content">
        <p>The key requirement here is that you&rsquo;ll need to <a href="https://www.jbang.dev/download/">install JBang</a> if you haven&rsquo;t already.</p>
      </div>
    </div><h2 id="the-code-a-standalone-mcp-server">The Code: A Standalone MCP Server</h2>
<p>Here is a complete, runnable MCP server in a single Java file. This server exposes a &ldquo;Calculator&rdquo; tool to any MCP-compatible LLM.</p>
<p>No build file, no project directory structure or anything.
<strong>Just a single Java file.</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">///usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS dev.langchain4j:langchain4j-core:1.11.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS dev.langchain4j:langchain4j-community-mcp-server:1.11.0-beta19</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS org.slf4j:slf4j-simple:2.0.17</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//JAVA 21</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.agent.tool.Tool</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.community.mcp.server.McpServer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.community.mcp.server.transport.StdioMcpServerTransport</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.slf4j.Logger</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.slf4j.LoggerFactory</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.List</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.concurrent.CountDownLatch</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">McpToolServer</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Important: Redirect logs to System.err</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">setProperty</span>(<span style="color:#4070a0">&#34;org.slf4j.simpleLogger.logFile&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                           </span><span style="color:#4070a0">&#34;System.err&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>Logger<span style="color:#bbb"> </span>log<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>LoggerFactory.<span style="color:#4070a0">getLogger</span>(McpToolServer.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>Exception<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Starting LangChain4j MCP Server...&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 1. Define your tools</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>CalculatorTools<span style="color:#bbb"> </span>tools<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>CalculatorTools();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 2. Wrap them in an McpServer</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>McpServer<span style="color:#bbb"> </span>server<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpServer(List.<span style="color:#4070a0">of</span>(tools));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 3. Connect to the STDIO transport</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>StdioMcpServerTransport<span style="color:#bbb"> </span>transport<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>StdioMcpServerTransport(server);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;MCP Server started successfully on STDIO.&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Keep the script alive</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>CountDownLatch(1).<span style="color:#4070a0">await</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Define MCP tools</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">CalculatorTools</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Calculates the sum of two numbers&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">double</span><span style="color:#bbb"> </span><span style="color:#06287e">add</span>(<span style="color:#902000">double</span><span style="color:#bbb"> </span>a,<span style="color:#bbb"> </span><span style="color:#902000">double</span><span style="color:#bbb"> </span>b)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Called add({}, {})&#34;</span>,<span style="color:#bbb"> </span>a,<span style="color:#bbb"> </span>b);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>a<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>b;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Calculates the square root of a number&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">double</span><span style="color:#bbb"> </span><span style="color:#06287e">sqrt</span>(<span style="color:#902000">double</span><span style="color:#bbb"> </span>x)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Called sqrt({})&#34;</span>,<span style="color:#bbb"> </span>x);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Math.<span style="color:#4070a0">sqrt</span>(x);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="breaking-it-down">Breaking It Down</h3>
<ul>
<li><strong>JBang Directives</strong>:
The <code>//DEPS</code> lines at the top handle all your dependencies.
When you run this file, JBang downloads everything automatically
(like Groovy&rsquo;s <a href="https://docs.groovy-lang.org/latest/html/documentation/grape.html">@Grab</a> annotation).</li>
<li><strong>The <code>@Tool</code> Annotation</strong>:
Any public method annotated with LangChain4j&rsquo;s <code>@Tool</code> annotation is automatically converted into a JSON-RPC tool specification that the LLM can understand.</li>
<li><strong><code>StdioMcpServerTransport</code></strong>:
Most local MCP clients communicate via Standard Input/Output.
This transport layer handles the JSON-RPC handshake for you.</li>
</ul>
<hr />
<h2 id="the-secret-sauce-logging-to-systemerr">The &ldquo;Secret Sauce&rdquo;: Logging to <code>System.err</code></h2>
<p>There is one critical rule for MCP servers running over STDIO: <strong><code>System.out</code> is for communication ONLY.</strong></p>
<p>I could have reduced the size of the example above almost by half by removing all the logging code.
But it&rsquo;s important to highlight it, if you want to be able to somehow log information along the way.</p>
<p>The MCP protocol uses <code>stdout</code> to send JSON-RPC messages back and forth.
If your application (or a library) prints a generic <code>INFO: Hello World</code> to <code>stdout</code>,
it will corrupt the JSON stream and crash the connection.</p>
<p>That’s why we use this static block:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">setProperty</span>(<span style="color:#4070a0">&#34;org.slf4j.simpleLogger.logFile&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;System.err&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>By forcing all logs to <code>stderr</code>, we keep the communication channel clean while still being able to see our logs in the terminal.</p>
<hr />
<h2 id="testing-your-server">Testing Your Server</h2>
<p>Before you plug it into an LLM, you can test it using the <strong><a href="https://modelcontextprotocol.io/docs/tools/inspector">MCP Inspector</a></strong>.
It’s a handy web UI that lets you see exactly what’s happening under the hood.</p>
<p>Run your server with the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npx @modelcontextprotocol/inspector jbang run --quiet McpToolServer.java
</span></span></code></pre></div>
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Notes</span>
      </div>
      <div class="admonition-content">
        <ul>
<li>You&rsquo;ll need to have <code>npx</code> installed to run the MCP inspector.</li>
<li>The <code>--quiet</code> flag tells JBang to stop printing build messages to stdout!
We don&rsquo;t want JBang to interfere with the STDIO protocol either!</li>
</ul>
      </div>
    </div><p>Once the inspector is running, you can click to connect to the server, list the tools, select a tool,
all in your browser, and watch your Java code execute in real-time.</p>
<p><figure>
  <a href="#img-adbed030017340d50425d7c511027fe9">
    <img src="/img/mcp/mcp-jbang-lc4j.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-adbed030017340d50425d7c511027fe9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/mcp-jbang-lc4j.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="running-it-in-gemini-cli">Running it in Gemini CLI</h2>
<p>To let Gemini use your new tool, add it to your <code>~/.gemini/settings.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;java-calc&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;command&#34;</span>: <span style="color:#4070a0">&#34;jbang&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;args&#34;</span>: [<span style="color:#4070a0">&#34;run&#34;</span>, <span style="color:#4070a0">&#34;--quiet&#34;</span>, <span style="color:#4070a0">&#34;/path/to/McpToolServer.java&#34;</span>]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now, when you ask Gemini &ldquo;What is the square root of 144?&rdquo;, it will reach out to your JBang script, execute the Java method, and give you the answer.</p>
<p><figure>
  <a href="#img-55d91b265d805edfd60139757202afa5">
    <img src="/img/mcp/mcp-jbang-lc4j-gemini-cli.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-55d91b265d805edfd60139757202afa5">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/mcp-jbang-lc4j-gemini-cli.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Well&hellip; maybe it won&rsquo;t call the tool because the LLM already knows the answer to such a simple question &#x1f603;
thanks to its training data and understanding of simple math, but for more specific and complex tools, it should be called!
In my case, it actually figured out it should call it.
And the response seems correct to me &#x1f603;</p>
<p><figure>
  <a href="#img-15004912b9415815f79966ca8f9f87d7">
    <img src="/img/mcp/jbang-mcp-result.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-15004912b9415815f79966ca8f9f87d7">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/jbang-mcp-result.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<hr />
<h2 id="wrapping-up">Wrapping Up</h2>
<p>Building MCP servers doesn&rsquo;t have to be complex.
With <strong>LangChain4j</strong> and <strong>JBang</strong>, you get the best of both worlds: the power of the Java ecosystem with the agility of a scripting language!</p>
<p>So next time you need to give an LLM access to a legacy Java library or a complex calculation, remember:
you’re only one <code>@Tool</code> annotation away.</p>
<p>Java developers can be as agile as all the script kiddies!
Happy MCP server coding!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Advanced RAG — Understanding Reciprocal Rank Fusion in Hybrid Search</title><link>https://glaforge.dev/posts/2026/02/10/advanced-rag-understanding-reciprocal-rank-fusion-in-hybrid-search/</link><pubDate>Tue, 10 Feb 2026 08:52:11 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/10/advanced-rag-understanding-reciprocal-rank-fusion-in-hybrid-search/</guid><description>&lt;p>Today, let&amp;rsquo;s come back to one of my favorite generative AI topics:
&lt;a href="https://glaforge.dev/tags/retrieval-augmented-generation/">Retrieval Augmented Generation&lt;/a>, or RAG for short.&lt;/p>
&lt;p>In RAG, the quality of your &lt;em>generation&lt;/em> (when an LLM crafts its answer based on search results)
is only as good as your &lt;em>retrieval&lt;/em> (the actually retrieved search results).&lt;/p>
&lt;p>While vector search (semantic) and keyword search (&lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25&lt;/a>) each have their strengths,
combining them often yields the best results.
That&amp;rsquo;s what we often call &lt;strong>Hybrid Search&lt;/strong>: combining two search techniques or the results of different searches with slight variations.&lt;/p></description><content:encoded>
<![CDATA[<p>Today, let&rsquo;s come back to one of my favorite generative AI topics:
<a href="https://glaforge.dev/tags/retrieval-augmented-generation/">Retrieval Augmented Generation</a>, or RAG for short.</p>
<p>In RAG, the quality of your <em>generation</em> (when an LLM crafts its answer based on search results)
is only as good as your <em>retrieval</em> (the actually retrieved search results).</p>
<p>While vector search (semantic) and keyword search (<a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a>) each have their strengths,
combining them often yields the best results.
That&rsquo;s what we often call <strong>Hybrid Search</strong>: combining two search techniques or the results of different searches with slight variations.</p>
<p>But how do you meaningfully combine a cosine similarity score of <code>0.85</code> (from vector search) with a BM25 score of <code>12.4</code>?
Those values are on two distinct unrelated scales!</p>
<p>Enter <strong>Reciprocal Rank Fusion (RRF)</strong>.</p>
<p>I vibe-coded a little <a href="https://storage.googleapis.com/public-bucket-for-demos/index.html">RRF simulator</a>
that shows how two lists of documents are ranked into one.
For the impatient, feel free to go ahead and play with it,
otherwise, you&rsquo;ll find more information at the bottom of this article
on how to use this simulator.</p>
<p><a href="https://storage.googleapis.com/public-bucket-for-demos/index.html"><figure>
  <a href="#img-ce3bbeff647065d370e0f565725e5951">
    <img src="/img/rag/rrf-simulator.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ce3bbeff647065d370e0f565725e5951">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/rag/rrf-simulator.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<h2 id="what-is-rrf">What is RRF?</h2>
<p>RRF is a robust, <em>&ldquo;zero-shot&rdquo;</em> algorithm for merging search results from different retrieval methods.
The technique was formally introduced by Gordon V. Cormack and his colleagues in their 2009 SIGIR paper,
<a href="https://cormack.uwaterloo.ca/cormacksigir09-rrf.pdf"><strong>&ldquo;Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods&rdquo;</strong></a>.</p>
<p>Instead of trying to somehow normalize arbitrary scores, RRF ignores the scores entirely and focuses on <strong>rank</strong>.</p>
<p>It operates on a simple premise: <strong>Documents that appear at the top of multiple lists are likely the most relevant.</strong>
In their research, the authors found that RRF consistently outperformed individual search systems and more complex fusion methods,
providing a stable and scalable way to combine diverse ranking signals.</p>
<p><figure>
  <a href="#img-9d58008cfc354468af6413247f29b6cf">
    <img src="/img/sketchnotes/rrf-1.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9d58008cfc354468af6413247f29b6cf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sketchnotes/rrf-1.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="the-formula-simplified">The Formula Simplified</h2>
<p>The RRF score for a document is calculated as:</p>

  <div class="math-container">
    <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>Score</mtext><mo stretchy="false">(</mo><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><munder><mo>∑</mo><mrow><mi>r</mi><mo>∈</mo><mi>R</mi></mrow></munder><mfrac><mn>1</mn><mrow><mi>k</mi><mo>+</mo><mtext>rank</mtext><mo stretchy="false">(</mo><mi>r</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">
\text{Score}(d) = \sum_{r \in R} \frac{1}{k + \text{rank}(r, d)}
</annotation></semantics></math></span>
  </div>

<ul>
<li><strong>
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>rank</mtext><mo stretchy="false">(</mo><mi>r</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\text{rank}(r, d)</annotation></semantics></math></span>

</strong>: The position of the document in a specific search result list (1st, 2nd, etc.).</li>
<li><strong>
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

</strong>: A smoothing constant, typically set to <strong>60</strong>.</li>
</ul>
<h3 id="why-is-k-so-important">Why is 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

 so important?</h3>
<p><figure>
  <a href="#img-3047a52f34cde515564835c3abc01e25">
    <img src="/img/sketchnotes/rrf-2.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3047a52f34cde515564835c3abc01e25">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sketchnotes/rrf-2.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Think of 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

 as a &ldquo;balance&rdquo; dial.</p>
<ul>
<li><strong>If 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

 is low (e.g., 1):</strong> The formula gives a massive advantage to the top-ranked items.
This configuration favors <strong>Precision</strong> — trusting that the absolute top results are correct and allowing a single high-performing retriever to dominate.</li>
<li><strong>If 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

 is high (e.g., 60):</strong> The advantage of being #1 shrinks. This configuration improves <strong>Recall</strong> and <strong>Consensus</strong>.
It ensures that even if the &ldquo;perfect&rdquo; result is buried at rank #10 across multiple lists, it will still rise to the top.</li>
</ul>
<p><strong>Why use 60?</strong>
By setting 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>=</mo><mn>60</mn></mrow><annotation encoding="application/x-tex">k=60</annotation></semantics></math></span>

 (the industry standard), RRF prioritizes <strong>consensus</strong> over individual outliers.
It ensures that a document appearing consistently (e.g., ranked #10 in <em>both</em> keyword and vector search) will score higher than a document that is #1 in only one list but completely missing from the others.</p>
<p>It rewards documents that <strong>multiple</strong> algorithms agree on, rather than letting a single outlier dominate the results.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>A Note on Document Sets</span>
      </div>
      <div class="admonition-content">
        <p>RRF is fundamentally designed to find <strong>consensus</strong>.
This means it works best when your different retrieval methods are looking at the same overall set of documents and return some overlaps.</p>
<p>If your search results are <strong>totally disjoint</strong> (meaning no document appears in more than one list),
RRF will simply interleave the results: you&rsquo;ll get the #1 from list A, then #1 from list B, followed by the #2 from list A, and so on.
The algorithm only truly begins to &ldquo;fuse&rdquo; and re-sort the results when documents start appearing in multiple lists.</p>
      </div>
    </div><h2 id="why-use-rrf-in-rag">Why Use RRF in RAG?</h2>
<ol>
<li><strong>Normalization Free:</strong> You don&rsquo;t need to know the distribution of your vector or BM25 scores. RRF works purely on position.</li>
<li><strong>Scalability:</strong> It&rsquo;s extremely efficient for sharded, billion-scale indices where global score normalization is expensive.</li>
<li><strong>Candidate Selection:</strong> RRF is an excellent &ldquo;first stage&rdquo; reranker. A common pattern is to retrieve the top 100 documents via RRF,
and then use a more expensive (but precise) <a href="https://medium.com/@sujathamudadla1213/what-is-cross-encoder-fec22b58f16c">Cross-Encoder</a>
(a <em>reranker</em> model) to rank the top 10 for the LLM context window (see below more for details).</li>
</ol>
<h2 id="the-two-stage-architecture-rrf--cross-encoder">The Two-Stage Architecture: RRF + Cross-Encoder</h2>
<p><figure>
  <a href="#img-6fbea3539afb6aadf3541909e79059a7">
    <img src="/img/sketchnotes/rrf-3.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6fbea3539afb6aadf3541909e79059a7">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sketchnotes/rrf-3.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>While RRF is excellent at merging lists, it lacks deep semantic understanding of the query-document relationship.
This is where <strong>Cross-Encoders</strong> shine — models like <a href="https://en.wikipedia.org/wiki/BERT_(language_model)">BERT</a>
that score the actual interaction between query and text.
However, they are computationally expensive and slow.</p>
<p>The industry standard pattern is a &ldquo;Two-Stage&rdquo; architecture:</p>
<ol>
<li><strong>Stage 1 (Candidate Selection):</strong> Use Hybrid Search (Vector + Keyword) fused with <strong>RRF</strong> to retrieve a broad pool of candidates (e.g., top 100).
This ensures high <strong>Recall</strong> — the right answer is likely <em>somewhere</em> in this list.</li>
<li><strong>Stage 2 (Precision Reranking):</strong> Pass only these top 100 candidates to a <strong>Cross-Encoder</strong>.
The model re-scores them based on deep relevance, picking the absolute best 5-10 chunks for the LLM&rsquo;s context window.</li>
</ol>
<p>This pipeline gives you the best of both worlds: the speed and breadth of RRF with the precision of a Cross-Encoder.</p>
<h2 id="going-further-rag-fusion">Going Further: RAG-Fusion</h2>
<p><figure>
  <a href="#img-c296f20bb5e40558b8f8adf2e92d8f83">
    <img src="/img/sketchnotes/rrf-4.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c296f20bb5e40558b8f8adf2e92d8f83">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sketchnotes/rrf-4.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>RAG-Fusion takes the hybrid approach a step further.
This technique was introduced by Zackary Rackauckas in the 2024 paper,
<a href="https://arxiv.org/abs/2402.03367"><strong>&ldquo;RAG-Fusion: a New Take on Retrieval-Augmented Generation&rdquo;</strong></a>.
It uses an LLM to generate multiple variations of the user&rsquo;s original query to &ldquo;broaden the net&rdquo; and find relevant context that a single query might miss.</p>
<p>The process follows a clever loop:</p>
<ol>
<li><strong>Multi-Query Generation:</strong> An LLM generates 3-5 different versions of the user&rsquo;s query (e.g., synonyms, rephrasings, or breaking down a complex question).</li>
<li><strong>Parallel Retrieval:</strong> Each variation is sent to the search engine (both Vector and Keyword).</li>
<li><strong>RRF Aggregation:</strong> All resulting lists are fused using RRF.</li>
</ol>
<p>By using RRF to merge results from <em>multiple</em> query variations, the system naturally filters out &ldquo;topic drift.&rdquo;
Documents that appear consistently across many query variants rise to the top, while noise from a single poor query variation is pushed down.
This &ldquo;consensus&rdquo; approach significantly reduces hallucination rates by ensuring the LLM is provided with content validated by multiple search angles.</p>
<h2 id="rrf-in-the-wild-langchain4j">RRF in the Wild: LangChain4j</h2>
<p>RRF isn&rsquo;t just a theoretical concept; it&rsquo;s a standard component in modern AI stacks. <a href="https://docs.langchain4j.dev"><strong>LangChain4j</strong></a>,
the popular Java library for building LLM-powered applications, uses RRF as its default mechanism for aggregating results from multiple sources.</p>
<p>The <code>DefaultContentAggregator</code> class in LangChain4j employs a <code>ReciprocalRankFuser</code> to merge ranked lists of content.
This means if you configure a RAG pipeline with multiple retrievers (e.g., one for recent web data and one for internal documents),
LangChain4j automatically applies RRF to give you the best of both worlds without any manual tuning.</p>
<p>Here is how you can set up a hybrid retrieval system in LangChain4j that implicitly uses RRF:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// 1. Define your retrievers</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ContentRetriever<span style="color:#bbb"> </span>bm25Retriever<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>...;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ContentRetriever<span style="color:#bbb"> </span>vectorSearchRetriever<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>EmbeddingStoreContentRetriever.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">embeddingStore</span>(embeddingStore)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">embeddingModel</span>(embeddingModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxResults</span>(10)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 2. Combine them in the RetrievalAugmentor</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// DefaultRetrievalAugmentor uses</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// DefaultContentAggregator which uses RRF</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>RetrievalAugmentor<span style="color:#bbb"> </span>retrievalAugmentor<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>DefaultRetrievalAugmentor.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">contentRetriever</span>(bm25Retriever)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">contentRetriever</span>(vectorSearchRetriever)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 3. Configure the augmentor on the AI service</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Assistant<span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(Assistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">retrievalAugmentor</span>(retrievalAugmentor)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>By simply adding multiple retrievers, the <code>DefaultContentAggregator</code> kicks in, calculating the 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mrow><mi>k</mi><mo>+</mo><mtext>rank</mtext></mrow></mfrac></mrow><annotation encoding="application/x-tex"> \frac{1}{k + \text{rank}} </annotation></semantics></math></span>

 score for every item found by <em>either</em> retriever and re-sorting them into a single, high-quality context for your LLM.</p>
<h2 id="try-the-simulator">Try the Simulator</h2>
<p>To truly understand how the smoothing constant 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

 impacts rankings and how different lists merge, I&rsquo;ve built a <strong>Reciprocal Rank Fusion Simulator</strong>.</p>
<p>You can experiment with different document rankings and see the fusion math in real-time here:
👉 <strong><a href="https://storage.googleapis.com/public-bucket-for-demos/index.html">Launch RRF Simulator</a></strong></p>
<p>Use this tool to visualize how RRF balances precision (favoring top ranks) vs. consensus (favoring agreement across lists) and tune your intuition for hybrid search architectures.</p>
<ul>
<li><strong>Interactive Rank Experimentation</strong>:
Create new lists, add/remove documents, shuffle the lists, etc.
Use drag-and-drop to reorder results in two independent search engines.
Since RRF ignores raw scores and focuses only on position,
you can see exactly how moving a document up or down one list impacts its final &ldquo;fused&rdquo; standing.</li>
<li><strong>Visualizing Consensus</strong>:
The simulation demonstrates RRF’s &ldquo;consensus&rdquo; logic, indeed
documents that appear in both lists (even at mediocre ranks)
often outperform documents that rank #1 in only one list.
This highlights why hybrid search is so effective.</li>
<li><strong>Real-Time Parameter Tuning</strong>:
By adjusting the 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span>

 constant, you can see how the algorithm&rsquo;s sensitivity changes.
You&rsquo;ll observe how a lower prioritizes &ldquo;top-heavy&rdquo; results,
while a higher gives more weight to documents found deeper in the search results.</li>
</ul>
<h2 id="summary">Summary</h2>
<p>I hope this article helped you get a better <strong>intuition</strong> of <strong>Reciprocal Rank Fusion</strong>, why it&rsquo;s so useful, and how it works!
By <strong>focusing on rank</strong> rather than arbitrary scores, RRF provides a robust and scalable way to merge diverse search results,
making it a cornerstone of <strong>modern hybrid search</strong> and <strong>advanced RAG architectures</strong>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Decoded: How Google AI Studio Securely Proxies Gemini API Requests</title><link>https://glaforge.dev/posts/2026/02/09/decoded-how-google-ai-studio-securely-proxies-gemini-api-requests/</link><pubDate>Mon, 09 Feb 2026 08:52:18 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/09/decoded-how-google-ai-studio-securely-proxies-gemini-api-requests/</guid><description>&lt;p>If you&amp;rsquo;ve recently vibe-coded and exported a Gemini-powered app from &lt;a href="https://aistudio.google.com/">Google AI Studio&lt;/a>
to host it online on Google &lt;a href="https://cloud.google.com/run">Cloud Run&lt;/a>,
you might have noticed a &lt;code>server/&lt;/code> directory containing a Node.js application.
This isn&amp;rsquo;t just a simple file server; it&amp;rsquo;s a clever &lt;em>&amp;ldquo;transparent proxy&amp;rdquo;&lt;/em> designed to solve a classic problem in frontend AI development:&lt;/p>
&lt;p>&lt;strong>How do I use my API key without leaking it to the browser?&lt;/strong>&lt;/p>
&lt;p>In this post (although vibe-coding is supposed to be all about &lt;em>not&lt;/em> looking at the code at all)
we&amp;rsquo;ll dissect exactly how this architecture works, why it&amp;rsquo;s safer than a client-side key, and where its security limits lie.&lt;/p></description><content:encoded>
<![CDATA[<p>If you&rsquo;ve recently vibe-coded and exported a Gemini-powered app from <a href="https://aistudio.google.com/">Google AI Studio</a>
to host it online on Google <a href="https://cloud.google.com/run">Cloud Run</a>,
you might have noticed a <code>server/</code> directory containing a Node.js application.
This isn&rsquo;t just a simple file server; it&rsquo;s a clever <em>&ldquo;transparent proxy&rdquo;</em> designed to solve a classic problem in frontend AI development:</p>
<p><strong>How do I use my API key without leaking it to the browser?</strong></p>
<p>In this post (although vibe-coding is supposed to be all about <em>not</em> looking at the code at all)
we&rsquo;ll dissect exactly how this architecture works, why it&rsquo;s safer than a client-side key, and where its security limits lie.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>When exporting/downloading an AI Studio generated app, you won&rsquo;t see what I&rsquo;m going to explain below.
It&rsquo;s when you export the app to Cloud Run that the mechanism explained here is put in place.
I looked at the code that was deployed on Cloud Run, from the Google Cloud Console.</p>
      </div>
    </div><h2 id="the-problem-client-side-keys">The Problem: Client-Side Keys</h2>
<p>When building an app with a React, Vue, or vanilla JS frontend that talks to an AI model, the path of least resistance would often be:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// ❌ DANGEROUS: Do not do this in production
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> API_KEY <span style="color:#666">=</span> <span style="color:#4070a0">&#34;AIzaSy...&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> genAI <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> GoogleGenerativeAI(API_KEY);
</span></span></code></pre></div><p>The moment you deploy this, your API key is visible in the browser&rsquo;s &ldquo;Network&rdquo; tab of the Dev tools, or by inspecting the JavaScript source.
A malicious actor can grab your key and use your quota for their own projects, potentially racking up bills or exhausting your limits.</p>
<p><figure>
  <a href="#img-0205ea0975b4053f37e692e625814967">
    <img src="/img/nano-banana/gloops-tokens-consumed.jpg"
      alt="Scary illustration of a person on his computer horrified to discover on his screen that his LLM tokens have been consumed because of a leaked API key"
       />
  </a>
  <figcaption>Scary illustration of a person on his computer horrified to discover on his screen that his LLM tokens have been consumed because of a leaked API key</figcaption>
</figure>
<div class="lightbox" id="img-0205ea0975b4053f37e692e625814967">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/gloops-tokens-consumed.jpg"
    alt="Scary illustration of a person on his computer horrified to discover on his screen that his LLM tokens have been consumed because of a leaked API key"
     />
  <div class="lightbox-caption">Scary illustration of a person on his computer horrified to discover on his screen that his LLM tokens have been consumed because of a leaked API key</div>
</div>
</p>
<h2 id="the-solution-the-transparent-proxy">The Solution: The &ldquo;Transparent&rdquo; Proxy</h2>
<p>&#x1f4a1; <a href="https://aistudio.google.com/">Google AI Studio</a>&rsquo;s exported starter code uses a <strong>Node.js proxy server</strong>
combined with <strong>Service Workers</strong> to hide the key while letting you write frontend code as if you were calling the API directly.</p>
<p>The classical approach is to have your frontend call your backend code, and it&rsquo;s only your backend code that has access to the API key.
Here, the approach taken by AI Studio for Cloud Run deployment is to let developers continue to write frontend code <em>as usual</em>,
calling the generative AI API as if it were a direct call from the frontend&hellip; But in reality,
the call is intercepted and proxied to a server backend that takes care of making the real call to the AI model,
and handle the API key on the backend, without ever exposing it.</p>
<h3 id="part-1-the-server">Part 1: The Server</h3>
<p>The heart of the system is an Express.js server (<code>server/server.js</code>). It serves your frontend files but also listens on a special endpoint: <code>/api-proxy</code>.</p>
<p>When a request hits this endpoint, the server:</p>
<ol>
<li><strong>Injects the API Key:</strong> It takes the key from a secure environment variable on the server-side (<code>GEMINI_API_KEY</code>).</li>
<li><strong>Forwards the Request:</strong> It sends the modified request to Google&rsquo;s real API (<code>generativelanguage.googleapis.com</code>).</li>
<li><strong>Streams the Response:</strong> It pipes the answer back to your browser.</li>
</ol>
<p>Here is the critical logic in <code>server.js</code> where the key is added:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// server/server.js
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// ... inside the /api-proxy route handler ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Prepare headers for the outgoing request
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> outgoingHeaders <span style="color:#666">=</span> {};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Copy most headers from the incoming request (content-type, etc.)
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// ... (code to copy headers) ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// 🔐 KEY INJECTION HAPPENS HERE
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>outgoingHeaders[<span style="color:#4070a0">&#39;X-Goog-Api-Key&#39;</span>] <span style="color:#666">=</span> process.env.GEMINI_API_KEY;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> axiosConfig <span style="color:#666">=</span> {
</span></span><span style="display:flex;"><span>    method<span style="color:#666">:</span> req.method,
</span></span><span style="display:flex;"><span>    url<span style="color:#666">:</span> <span style="color:#4070a0">`https://generativelanguage.googleapis.com/</span><span style="color:#70a0d0">${</span>targetPath<span style="color:#70a0d0">}</span><span style="color:#4070a0">`</span>,
</span></span><span style="display:flex;"><span>    headers<span style="color:#666">:</span> outgoingHeaders,
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Forward the request to Google
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> apiResponse <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> axios(axiosConfig);
</span></span></code></pre></div><p>The <strong>frontend never receives the key</strong>. It only receives the <em>results</em> of the API call.</p>
<h3 id="part-2-client-side-interception">Part 2: Client-Side Interception</h3>
<p>If you look at the frontend code (e.g., in <code>App.tsx</code>), you might see standard calls to the Gemini API
(usually it&rsquo;s implemented in a dedicated <code>services/geminiService.ts</code> file):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Frontend code looks like it&#39;s calling Google directly!
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> model <span style="color:#666">=</span> genAI.getGenerativeModel({
</span></span><span style="display:flex;"><span>  model<span style="color:#666">:</span> <span style="color:#4070a0">&#34;gemini-3-flash-preview&#34;</span>,
</span></span><span style="display:flex;"><span>  temperaturecontents<span style="color:#666">:</span> {...}
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> result <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> model.generateContent(prompt);
</span></span></code></pre></div><p>How does this work if the frontend doesn&rsquo;t have the key?</p>
<p>The server injects two scripts into your <code>index.html</code> at runtime:</p>
<ol>
<li><code>websocket-interceptor.js</code></li>
<li><code>service-worker.js</code></li>
</ol>
<h4 id="the-service-worker">The Service Worker</h4>
<p>The <code>service-worker.js</code> acts like a network traffic cop inside your browser. It monitors all outgoing fetch requests.
If it sees a request headed for <code>generativelanguage.googleapis.com</code>, it <strong>stops it</strong> and redirects it to your local server instead.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// server/public/service-worker.js
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> TARGET_URL_PREFIX <span style="color:#666">=</span> <span style="color:#4070a0">&#39;https://generativelanguage.googleapis.com&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>self.addEventListener(<span style="color:#4070a0">&#39;fetch&#39;</span>, (event) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> requestUrl <span style="color:#666">=</span> event.request.url;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">if</span> (requestUrl.startsWith(TARGET_URL_PREFIX)) {
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// ✋ Stop! Don&#39;t go to Google directly.
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#60a0b0;font-style:italic">// 👉 Go to our local proxy instead.
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">const</span> remainingPath <span style="color:#666">=</span> requestUrl.substring(TARGET_URL_PREFIX.length);
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">const</span> proxyUrl <span style="color:#666">=</span> <span style="color:#4070a0">`</span><span style="color:#70a0d0">${</span>self.location.origin<span style="color:#70a0d0">}</span><span style="color:#4070a0">/api-proxy</span><span style="color:#70a0d0">${</span>remainingPath<span style="color:#70a0d0">}</span><span style="color:#4070a0">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// Forward the request to /api-proxy
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    event.respondWith(fetch(<span style="color:#007020;font-weight:bold">new</span> Request(proxyUrl, { ... })));
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>This &ldquo;transparent&rdquo; redirection means you don&rsquo;t have to change your frontend code to point to <code>http://localhost:3000/api-proxy</code>.
You just write standard SDK code, and the Service Worker handles the routing.</p>
<h4 id="the-websocket-interceptor">The WebSocket Interceptor</h4>
<p>For streaming features or chat, the Gemini API uses WebSockets. Service Workers cannot easily intercept WebSocket connections,
so the solution uses a different trick: <strong>Monkey Patching</strong>.</p>
<p>The <code>websocket-interceptor.js</code> overwrites the global browser <code>WebSocket</code> constructor.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// server/public/websocket-interceptor.js
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> originalWebSocket <span style="color:#666">=</span> <span style="color:#007020">window</span>.WebSocket;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020">window</span>.WebSocket <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> <span style="color:#007020">Proxy</span>(originalWebSocket, {
</span></span><span style="display:flex;"><span>  construct(target, args) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">let</span> [url, protocols] <span style="color:#666">=</span> args;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// Check if the connection is destined for Gemini
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">if</span> (url.includes(<span style="color:#4070a0">&#39;generativelanguage.googleapis.com&#39;</span>)) {
</span></span><span style="display:flex;"><span>       <span style="color:#60a0b0;font-style:italic">// Redirect to our local proxy endpoint
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>       url <span style="color:#666">=</span> url.replace(<span style="color:#4070a0">&#39;wss://generativelanguage.googleapis.com&#39;</span>,
</span></span><span style="display:flex;"><span>                         <span style="color:#4070a0">`wss://</span><span style="color:#70a0d0">${</span><span style="color:#007020">window</span>.location.host<span style="color:#70a0d0">}</span><span style="color:#4070a0">/api-proxy`</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// Create the WebSocket with the new URL
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">return</span> <span style="color:#007020;font-weight:bold">new</span> originalWebSocket(url, protocols);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="security-reality-check">Security Reality Check</h2>
<p>Is this secure? <strong>Yes and No.</strong></p>
<h3 id="-the-good-credential-protection">✅ The Good: Credential Protection</h3>
<p>This architecture successfully hides the &ldquo;Secret String&rdquo; (your API Key), as if you had written your own backened server.</p>
<ul>
<li>It is <strong>not</strong> in the JavaScript bundle.</li>
<li>It is <strong>not</strong> in the network traffic (except between your server and Google).</li>
<li>A user cannot &ldquo;copy-paste&rdquo; your key to use in their own unrelated backend script.</li>
</ul>
<h3 id="-the-bad-the-open-proxy-risk">⚠️ The Bad: The &ldquo;Open Proxy&rdquo; Risk</h3>
<p>Because the server is a &ldquo;dumb pipe&rdquo; — it blindly signs <em>any</em> request sent to <code>/api-proxy</code>.
A malicious user on your site can still abuse your quota, by opening Chrome DevTools and running:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// This will be intercepted by the Service Worker and proxied!
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>fetch(<span style="color:#4070a0">&#39;https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent&#39;</span>, {
</span></span><span style="display:flex;"><span>    method<span style="color:#666">:</span> <span style="color:#4070a0">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>    body<span style="color:#666">:</span> JSON.stringify({ contents<span style="color:#666">:</span> [{ parts<span style="color:#666">:</span> [{ text<span style="color:#666">:</span> <span style="color:#4070a0">&#34;Generate 5000 words of spam...&#34;</span> }] }] })
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>Your server will happily stamp this request with your API key and send it to Google.</p>
<h3 id="-the-mitigation-rate-limiting">🛡️ The Mitigation: Rate Limiting</h3>
<p>The AI Studio team anticipated this. The generated server includes <strong>Rate Limiting</strong> to prevent a single user from draining your quota instantly.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// server/server.js
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> proxyLimiter <span style="color:#666">=</span> rateLimit({
</span></span><span style="display:flex;"><span>    windowMs<span style="color:#666">:</span> <span style="color:#40a070">15</span> <span style="color:#666">*</span> <span style="color:#40a070">60</span> <span style="color:#666">*</span> <span style="color:#40a070">1000</span>, <span style="color:#60a0b0;font-style:italic">// 15 minutes
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    max<span style="color:#666">:</span> <span style="color:#40a070">100</span>, <span style="color:#60a0b0;font-style:italic">// Limit each IP to 100 requests per window
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    message<span style="color:#666">:</span> <span style="color:#4070a0">&#39;Too many requests from this IP...&#39;</span>
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app.use(<span style="color:#4070a0">&#39;/api-proxy&#39;</span>, proxyLimiter);
</span></span></code></pre></div><p>This ensures that while a user <em>can</em> make requests via your proxy, they are capped at a lower speed
(e.g., 100 requests per 15 minutes, but that might still be a bit too much!)</p>
<h2 id="conclusion">Conclusion</h2>
<p>I&rsquo;ve always been curious to understand how Google AI Studio was protecting the Gemini API key,
although the code appeared to directly make use of the API key on the frontend.
But the (real) code that is actually deployed on Cloud Run is pretty smart,
making use of some interesting tricks to not expose the key, and to mitigate the abuse of your quota.</p>
<p>The Google AI Studio proxy server is a nice piece of engineering for <strong>prototyping and demos</strong>.
It allows for a &ldquo;serverless-feeling&rdquo; frontend development experience while adhering to the basic security rule of keeping API keys on the server.</p>
<p>However, for a <strong>production application</strong>, you should eventually replace this generic proxy with specific backend endpoints
(e.g., <code>/api/generate-recipe</code>, <code>/api/chat-response</code>) that:</p>
<ol>
<li>Validate user input (<em>&ldquo;Is this actually a recipe request?&rdquo;</em>).</li>
<li>Authenticate the user (<em>&ldquo;Is this user logged in?&rdquo;</em>).</li>
<li>Apply strict business logic before calling the Gemini API.</li>
</ol>
<p>Even if Google AI Studio protects your API key to some extent, your quota can still be exhausted by a malicious user.
So if you&rsquo;re exposing such an application to the public, ask AI Studio to either add authentication,
or to request the user to pass their own API key.
Or even both!</p>
<p>I hope you found this exploration interesting!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Latest Gemini and Nano Banana Enhancements in LangChain4j</title><link>https://glaforge.dev/posts/2026/02/06/latest-gemini-and-nano-banana-enhancements-in-langchain4j/</link><pubDate>Fri, 06 Feb 2026 17:58:34 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/06/latest-gemini-and-nano-banana-enhancements-in-langchain4j/</guid><description>&lt;p>A few days ago, &lt;a href="https://github.com/langchain4j/langchain4j/releases/tag/1.11.0">LangChain4j 1.11.0&lt;/a> was released,
and with this version, a few notable enhancements to the support of the Gemini model family have landed.
Let&amp;rsquo;s dive in!&lt;/p>
&lt;h2 id="new-image-generation-models-gemini-25--30-preview-aka-banana-nano-banana">New Image Generation Models (Gemini 2.5 &amp;amp; 3.0 Preview, aka &amp;#x1f34c; Nano Banana)&lt;/h2>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition note">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">&lt;path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/>&lt;/svg>
&lt;span>Note&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>Before showing some snippets of code, let me give you the link to the full documentation on the new image model:
&lt;a href="https://docs.langchain4j.dev/integrations/image-models/gemini">docs.langchain4j.dev/integrations/image-models/gemini&lt;/a>&lt;/p></description><content:encoded>
<![CDATA[<p>A few days ago, <a href="https://github.com/langchain4j/langchain4j/releases/tag/1.11.0">LangChain4j 1.11.0</a> was released,
and with this version, a few notable enhancements to the support of the Gemini model family have landed.
Let&rsquo;s dive in!</p>
<h2 id="new-image-generation-models-gemini-25--30-preview-aka-banana-nano-banana">New Image Generation Models (Gemini 2.5 &amp; 3.0 Preview, aka &#x1f34c; Nano Banana)</h2>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Before showing some snippets of code, let me give you the link to the full documentation on the new image model:
<a href="https://docs.langchain4j.dev/integrations/image-models/gemini">docs.langchain4j.dev/integrations/image-models/gemini</a></p>
      </div>
    </div><p>There&rsquo;s a new <code>GoogleAiGeminiImageModel</code> class which allows <em>text-to-image</em> generation and <em>image editing</em> using the latest &#x1f34c; <strong>Nano Banana</strong> models.</p>
<p>Supported Models:</p>
<ul>
<li><code>gemini-2.5-flash-image</code> <em>(Nano Banana)</em>: Optimized for speed.</li>
<li><code>gemini-3-pro-image-preview</code> <em>(Nano Banana Pro)</em>: High-fidelity, up to 4K resolution.</li>
</ul>
<p>Features:</p>
<ul>
<li><strong>Text-to-Image</strong>: Generate images from prompts.</li>
<li><strong>Image Editing</strong>: Edit existing images using text prompts (with optional mask support).</li>
<li><strong>Search Grounding</strong>: Ground image generation in Google Search results.</li>
</ul>
<h3 id="text-to-image-generation">Text-to-Image Generation</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>imageModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash-image&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">aspectRatio</span>(<span style="color:#4070a0">&#34;16:9&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">imageSize</span>(<span style="color:#4070a0">&#34;2K&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>imageModel.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;A cinematic shot of a futuristic city at sunset&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Save the generated image to a file</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Image<span style="color:#bbb"> </span>image<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">content</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>imageBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Base64.<span style="color:#4070a0">getDecoder</span>().<span style="color:#4070a0">decode</span>(image.<span style="color:#4070a0">base64Data</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Files.<span style="color:#4070a0">write</span>(Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;output.png&#34;</span>),<span style="color:#bbb"> </span>imageBytes);<span style="color:#bbb">
</span></span></span></code></pre></div><p>As you can see, different configuration parameters are possible;</p>
<ul>
<li><code>aspectRatio</code>: among <code>16:9</code>, <code>4:3</code>, <code>3:2</code>, <code>1:1</code>,  <code>2:3</code>, <code>3:4</code>, and <code>9:16</code></li>
<li><code>imageSize</code>: among <code>1K</code>, <code>2K</code>, <code>4K</code></li>
</ul>
<h3 id="image-generation-with-google-search-grounding">Image Generation with Google Search Grounding</h3>
<p>A powerful capability of Nano Banana Pro is the ability to ground its image generation in Google Search results,
with the <code>useGoogleSearchGrounding(true)</code> flag.</p>
<p>It&rsquo;s a model that&rsquo;s able to search for image references on the web, or for the latest information about a topic.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>groundedModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-3-pro-image-preview&#34;</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// search only in Pro</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">useGoogleSearchGrounding</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">aspectRatio</span>(<span style="color:#4070a0">&#34;1:1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>groundedResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>groundedModel.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    A kawaii illustration of the current weather forecast for Paris
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    showing the current temperature (in Celsius)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, we want to create a <em>kawaii</em> illustration of the <em>current</em> weather in Paris.
So Nano Banana Pro is going to <strong>search on Google</strong> to find about the weather forecast at this point in time!</p>
<p>At the time of this writing, the forecast is:</p>
<p><figure>
  <a href="#img-8eab344f7b8a2411e8f2c1ef23bf583f">
    <img src="/img/nano-banana/paris_weather_illustration.jpg"
      alt="A Kawaii illustration of a little cloud and sun characters indicating a temperature of 13°C in Paris"
       />
  </a>
  <figcaption>A Kawaii illustration of a little cloud and sun characters indicating a temperature of 13°C in Paris</figcaption>
</figure>
<div class="lightbox" id="img-8eab344f7b8a2411e8f2c1ef23bf583f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/paris_weather_illustration.jpg"
    alt="A Kawaii illustration of a little cloud and sun characters indicating a temperature of 13°C in Paris"
     />
  <div class="lightbox-caption">A Kawaii illustration of a little cloud and sun characters indicating a temperature of 13°C in Paris</div>
</div>
</p>
<p>Pretty <em>kawaii</em>, right? &#x1f603;</p>
<h2 id="google-maps-grounding">Google Maps Grounding</h2>
<p>You can now enable Google Maps grounding to allow the model to access real-world location data, including place IDs, addresses, and reviews.</p>

    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>This is currently available on the 2.5 models, not (yet?) the 3.0 models.</p>
      </div>
    </div><div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>chatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">allowGoogleMaps</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Enable Google Maps tool</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">retrieveGoogleMapsWidgetToken</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Optional: UI widget</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;Find the best restaurant near the Eiffel tower&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini might answer something along the lines of this
(with details coming from Maps, as star rating proves, not from its training knowledge):</p>
<blockquote>
<p>For a memorable dining experience near the Eiffel Tower, several
highly-rated restaurants offer a range of cuisines and atmospheres.</p>
<p><strong>French Cuisine with a View:</strong>
For an exceptional meal with stunning views, consider <strong>Jules Verne</strong>,
an elegant restaurant located within the Eiffel Tower itself,
boasting a 4.5-star rating. Another option is <strong>Francette</strong>,
a refined restaurant on a barge on the Seine, also with a 4.5-star
rating and direct views of the tower.</p>
<p><strong>Café de l’Homme</strong> is a stylish bistro in the Musée de l’Homme,
featuring outdoor tables with Eiffel Tower views and a 4.1-star
rating.</p>
<p><strong>Top-Rated French Bistros:</strong>
If a classic French bistro is more your style, <strong>De la Tour</strong> is
a popular family-run establishment with a 4.8-star rating.
<strong>Arnaud Nicolas</strong>, known for its artfully presented tasting menus,
has a 4.7-star rating. With a 4.8-star rating, <strong>Milagro</strong> is another
excellent choice. Also highly rated is <strong>Le CasseNoix</strong>, a charming,
retro spot with a 4.7-star rating.</p>
<p><strong>Italian Options:</strong>
If you&rsquo;re in the mood for Italian food, <strong>Chez Pippo</strong> is a cozy
trattoria with a 4.6-star rating. <strong>La Casa di Alfio</strong> is another
popular choice with a 4.5-star rating. With a 4.7-star rating,
<strong>In Casa</strong> is also nearby. And <strong>Pink Mamma</strong> has an impressive
4.7-star rating with over 45,000 reviews.</p></blockquote>
<h2 id="google-search-grounding">Google Search Grounding</h2>
<p>Standard text generation can now be grounded using Google Search, ensuring responses are based on up-to-date web information.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>chatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-3-flash-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">allowGoogleSearch</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Enable Google Search tool</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;What are the latest models from OpenAI, Anthropic, and Google?&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="url-context-tool">URL Context Tool</h2>
<p>This feature allows the model to access and use information directly from specific URLs provided in the prompt context.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>chatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-3-flash-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">allowUrlContext</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Enable URL Context tool</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The model can now fetch and reason</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// over content from URLs in the prompt</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">chat</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Check Guillaume Laforge&#39;s blog archive at
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    https://glaforge.dev/archive/
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    and tell me how many articles he wrote in January 2026
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Suffice to have URLs in your prompt.
<strong>No need to fetch or scrape</strong> the content yourself ahead of making the LLM call.</p>
<h2 id="multimodal-agents-image-generation">Multimodal Agents (Image Generation)</h2>
<p><code>AiServices</code> now supports returning generated images directly, enabling the creation of multimodal agents that can produce visual content.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">CreativeAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@UserMessage</span>(<span style="color:#4070a0">&#34;Generate a high-quality image of {{description}}&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ImageContent<span style="color:#bbb"> </span><span style="color:#06287e">generateArtwork</span>(<span style="color:#555;font-weight:bold">@V</span>(<span style="color:#4070a0">&#34;description&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>description);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>CreativeAssistant<span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">builder</span>(CreativeAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatModel</span>(GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-3-pro-image-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ImageContent<span style="color:#bbb"> </span>artwork<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>assistant.<span style="color:#4070a0">generateArtwork</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;a cyberpunk street food stall&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then you can retrieve the image via <code>artwork.image().base64data()</code> and save it.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>In this example, we&rsquo;re using Nano Banana Pro!
Nano Banana is actually a chat model that has 2 response modalities:
text and images.</p>
<p>In the case of the <code>GeminiAiImageModel</code>, we were only requesting images to be generated. No text.</p>
      </div>
    </div><p><figure>
  <a href="#img-76e010b91e91660a94192c55dae861b3">
    <img src="/img/nano-banana/cyberpunk-food-stall.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-76e010b91e91660a94192c55dae861b3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/cyberpunk-food-stall.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="gemini-30-thinking-configuration">Gemini 3.0 Thinking Configuration</h2>
<p>You can configure the <em>&ldquo;thinking&rdquo;</em> process (Chain-of-Thought) for Gemini 3.0 models, allowing you to control the depth of reasoning.</p>
<p>Thinking levels available: <code>MINIMAL</code>, <code>LOW</code>, <code>MEDIUM</code>, <code>HIGH</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>thinkingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-3-flash-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">sendThinking</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">   </span><span style="color:#60a0b0;font-style:italic">// Send thinking process to the model</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">returnThinking</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Return thought process in the response</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">thinkingConfig</span>(GeminiThinkingConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">thinkingLevel</span>(GeminiThinkingLevel.<span style="color:#4070a0">HIGH</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Reasoning depth</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>thinkingModel.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;Solve this complex logic puzzle...&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="enhanced-metadata--token-usage">Enhanced Metadata &amp; Token Usage</h2>
<p>Responses from both Chat and Image models now include richer metadata,
including detailed token usage and grounding source information
(e.g., which web pages or map locations were used).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">chat</span>(request);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Cast to the Gemini specific response type</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// to get access to the metadata provided by Gemini</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>GoogleAiGeminiChatResponseMetadata<span style="color:#bbb"> </span>metadata<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>(GoogleAiGeminiChatResponseMetadata)<span style="color:#bbb"> </span>response.<span style="color:#4070a0">metadata</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Access Grounding Metadata</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(metadata.<span style="color:#4070a0">groundingMetadata</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>metadata.<span style="color:#4070a0">groundingMetadata</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">groundingChunks</span>().<span style="color:#4070a0">forEach</span>(chunk<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(chunk.<span style="color:#4070a0">web</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Source: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>chunk.<span style="color:#4070a0">web</span>().<span style="color:#4070a0">title</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34; (&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>chunk.<span style="color:#4070a0">web</span>().<span style="color:#4070a0">uri</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;)&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="summary">Summary</h2>
<p>These <strong>Gemini-related enhancements in LangChain4j 1.11.0</strong> further expand the capabilities of the Gemini integration.</p>
<p>From advanced <strong>image generation and editing with Nano Banana</strong> (Gemini 2.5 and 3.0 Preview)
to powerful <strong>grounding features with Google Search and Google Maps</strong>,
developers can now build more intelligent and context-aware applications.</p>
<p>The introduction of the <strong>URL Context tool</strong>, <strong>multimodal agents</strong>,
and <strong>configurable thinking processes</strong> for Gemini 3.0 allow for richer interactions and more precise control over model behavior.</p>
<p>The improved <strong>metadata and token usage reporting</strong> also provide valuable insights
for optimizing and understanding model responses.</p>
<p>Have fun with Gemini!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Researching Topics in the Age of AI — Rock-Solid Webhooks Case Study</title><link>https://glaforge.dev/posts/2026/02/04/researching-topics-in-the-age-of-ai-rock-solid-webhooks-case-study/</link><pubDate>Wed, 04 Feb 2026 15:06:17 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/04/researching-topics-in-the-age-of-ai-rock-solid-webhooks-case-study/</guid><description>&lt;p>Back in 2019, I spent significant time researching &lt;strong>Webhooks&lt;/strong>.
In particular, I was interested in best practices, pitfalls, design patterns,
and approaches for implementing Webhooks in a reliable, resilient, and effective way.&lt;/p>
&lt;p>Everything is distilled in that article:
&lt;a href="https://glaforge.dev/talks/2019/11/25/implementing-webhooks-not-as-trivial-as-it-may-seem/">Implementing Webhooks, not as trivial as it may seem&lt;/a>&lt;/p>
&lt;p>It likely took me a full week to dive deep into this subject, finding sources and
experimenting with design patterns myself.
But nowadays, AI makes it easier to dive deeper into topics, explore unfamiliar aspects,
and share findings with your team.&lt;/p></description><content:encoded>
<![CDATA[<p>Back in 2019, I spent significant time researching <strong>Webhooks</strong>.
In particular, I was interested in best practices, pitfalls, design patterns,
and approaches for implementing Webhooks in a reliable, resilient, and effective way.</p>
<p>Everything is distilled in that article:
<a href="https://glaforge.dev/talks/2019/11/25/implementing-webhooks-not-as-trivial-as-it-may-seem/">Implementing Webhooks, not as trivial as it may seem</a></p>
<p>It likely took me a full week to dive deep into this subject, finding sources and
experimenting with design patterns myself.
But nowadays, AI makes it easier to dive deeper into topics, explore unfamiliar aspects,
and share findings with your team.</p>
<p>As I built a research agent based on Google&rsquo;s <a href="https://ai.google.dev/gemini-api/docs/deep-research">Deep Research agent</a>,
I wanted to see how far it&rsquo;d go with a topic I had covered a while ago.</p>
<p>Armed with my <a href="https://glaforge.dev/posts/2026/01/03/building-a-research-assistant-with-the-interactions-api-in-java/">custom research agent</a>
(via the <a href="/posts/2026/01/30/a-javelit-frontend-for-the-deep-research-agent.md">Javelit frontend</a> I built around it),
I entered the query: <code>Webhook best practices for rock solid and resilient deployments</code>.</p>
<p>Gemini 3 Flash gave me a list of topics associated with that theme, and I selected the following ones:</p>
<blockquote>
<ul>
<li>Cryptographic signature verification using HMAC-SHA256</li>
<li>Implementing idempotency keys to prevent duplicate event processing</li>
<li>Asynchronous processing architectures using message queues and background workers</li>
<li>Preventing replay attacks with timestamp validation and nonces</li>
<li>Retry strategies using exponential backoff with jitter</li>
<li>Dead letter queue (DLQ) implementation and management for failed deliveries</li>
<li>Security through Mutual TLS (mTLS) and IP allowlisting</li>
<li>Webhook payload versioning and backward compatibility strategies</li>
<li>Handling high-volume event bursts with rate limiting and buffering</li>
<li>Circuit breaker patterns to protect downstream services from failure</li>
<li>Schema validation and data minimization in webhook payloads</li>
</ul></blockquote>
<p>There&rsquo;s significant overlap with the topics I covered in my old presentation!
I talked about idempotency, signatures, dead-letter queues, IP allowlisting, rate limiting &amp; buffering, etc.
This validates my previous findings and shows that today&rsquo;s generative AI
capabilities can identify the same key topics.</p>
<p>So I let Deep Research explore those topics.
After a few minutes, my agent wrote a long report in a concise style
(I used prompts to steer the report generation toward concise writing and avoid &ldquo;AI Slop&rdquo;).
Then Gemini 3 Pro created a summary, and Nano Banana Pro generated a sketchnote illustration.</p>
<p>Let&rsquo;s start with the illustration (the same you saw as the banner for this post):</p>
<h2 id="infographics">Infographics</h2>
<p><figure>
  <a href="#img-87b43c549c5fbb8f70335e3dc246c199">
    <img src="/img/gemini/webhooks-sketchnote.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-87b43c549c5fbb8f70335e3dc246c199">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/webhooks-sketchnote.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I have a special prompt to generate such sketchnotes, which I find readable, and appealing to share on social media:</p>
<pre tabindex="0"><code>Create a hand-drawn and hand-written sketchnote style
summary infographic, with a pure white background,
use fluo highlighters for the key points,
about the following information: ...
</code></pre><p>And I pass the summary generated by Gemini 3 Pro below.</p>
<h2 id="summary">Summary</h2>
<p>The summary is generated via this prompt, passing the entire research:</p>
<pre tabindex="0"><code>Create a concise summary of the research below.
Go straight to the summary; don&#39;t introduce it
(don&#39;t write &#34;Here&#39;s a summary...&#34; or equivalent).
</code></pre><p>For this research on Webhooks, here&rsquo;s what the summary was.
The summary&rsquo;s structure also guided the organization of the sketchnote above:</p>
<blockquote>
<p>Webhooks facilitate real-time, event-driven communication between systems but require a defensive architecture to ensure security, reliability, and scalability. Unlike polling, webhooks utilize a &ldquo;push&rdquo; model, necessitating robust handling of network partitions, malicious activity, and traffic spikes.</p>
<p><strong>Security and Authentication</strong> Security implementation must go beyond obscured URLs. The industry standard involves <strong>HMAC-SHA256 signature verification</strong> to ensure payload integrity and authenticity. Critical implementation details include using constant-time string comparisons to prevent timing attacks and validating raw, unparsed payloads. To prevent replay attacks, systems should enforce timestamp tolerance windows and utilize nonces. While Mutual TLS (mTLS) offers a higher security standard for zero-trust environments, it introduces significant complexity compared to signatures and IP allowlisting.</p>
<p><strong>Reliability and Architecture</strong> Because webhooks typically guarantee &ldquo;at-least-once&rdquo; delivery, receivers must implement <strong>idempotency</strong> using unique event keys and atomic storage to prevent duplicate processing from corrupting data. To handle high throughput and avoid timeouts, architectures should be asynchronous: an ingestion layer should immediately acknowledge requests (returning 202 Accepted) and offload the payload to a message queue for background processing by workers.</p>
<p><strong>Failure Handling and Recovery</strong> Robust systems employ <strong>exponential backoff with jitter</strong> for retries to prevent &ldquo;thundering herd&rdquo; scenarios that could overwhelm the receiver. Messages that fail all retry attempts should be routed to a <strong>Dead Letter Queue (DLQ)</strong> for inspection and potential redrive rather than being discarded. Additionally, <strong>circuit breakers</strong> are essential to pause delivery to failing endpoints, protecting the infrastructure from cascading failures during outages.</p>
<p><strong>Scalability and Payload Design</strong> To manage bursty traffic, providers should enforce rate limiting and buffering. Payload design involves a trade-off between &ldquo;Fat&rdquo; payloads (full state, convenient but larger attack surface) and &ldquo;Thin&rdquo; payloads (notifications only, secure but require callback API calls). Best practices suggest keeping payloads under 20kb, minimizing PII, and utilizing additive versioning per event type to maintain backward compatibility.</p></blockquote>
<p>Even <em>just</em> the summary, as is, is already valuable when researching a subject, as it gives you all the key pointers to go further.</p>
<h2 id="generating-a-slide-deck">Generating a Slide Deck</h2>
<p>I delivered my Webhook talk at a handful of conferences in Europe.
And of course, I needed a slide deck!
I often spend significant time on my decks, as I want them to be polished, clear, and beautiful.
Usually, I try to reduce the amount of text in favor of visual representations like diagrams and photographic illustrations.</p>
<p>Right, so I have a report and an infographic, but what about the deck?
My agent doesn&rsquo;t (yet?) handle that, so instead, I turned to <a href="https://notebooklm.google.com/">NotebookLM</a>,
gave it my research, and asked it to generate a slide deck with a particular design:</p>
<blockquote>
<p>A slide deck for a technical audience, describing all the best practices to implement rock solid resilient webhooks.
Opt for a blueprint architectural style, with illustrations.</p></blockquote>
<p>And it complied, generating the following deck, which I could see myself presenting:</p>
<script async class="speakerdeck-embed" data-id="1e129493512f4eb6bf05cbaad05ec4ad" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>The style is consistent across the deck and quite beautiful.
I usually put less text on slides, but it works here.
Under the hood, NotebookLM uses &#x1f34c; <strong>Nano Banana Pro</strong> (aka Gemini 3 Pro Image),
and the included graphics look spot on, sharp, and accurate.
I didn&rsquo;t even spot typos in the generated text.</p>
<h2 id="going-further-should-i-share-those">Going Further: Should I share those?</h2>
<p>More and more I use AI in my work, but I still prefer writing my articles by hand.
I can use generative AI to do a first draft, explain a piece of code, or come up with a conclusion.
I also use image generation to create either illustrations or sketchnotes.
But otherwise, it&rsquo;s still me. My writing, my design, my style, my authentic voice.</p>
<p>What I&rsquo;m wondering though is what to do with such research reports.
I run such reports to explore a particular topic, to avoid forgetting some key angle or aspect.
But I usually keep that research for me (sometimes saved inside my <a href="https://obsidian.md/">Obsidian</a> vault, or as Google Docs here and there)</p>
<p>This week, I had a nice lunch with a couple old friends who were thinking that it was <strong>worth sharing those reports</strong> more widely, rather than keeping them private
(one of my friend was sharing his research publicly in a GitHub repository.)</p>
<p>On the one hand, I don&rsquo;t want to <strong>increase the quantity of AI slop available on the internet</strong>,
but on the other hand, once they&rsquo;re generated, it&rsquo;s sad to see all those tokens wasted, and benefiting me exclusively!
I would clearly label them as <em>AI generated research reports</em> or similar though, but maybe others would find those useful and interesting?</p>
<p>I&rsquo;d be <strong>curious to hear your thoughts</strong> on this!
Don&rsquo;t hesitate to share them with me on social media.</p>
<p>Generative AI, assisted with agents that do targeted web searches, really changes the game in terms of research.
Tools like <a href="https://notebooklm.google.com/">NotebookLM</a> are able to find the right sources of information, and can generate all sorts of artifacts and visualisations
(audio and video podcasts, mindmaps, infographics, quizes, etc.)
And image models like &#x1f34c; <strong>Nano Banana</strong> are incredible and able to generate very clear visuals.
This is really an interesting era to learn more about any topics, and at a much greater depth than scouring your favorite search engine manually!
Deep Research and NotebookLM give you the URLs of the sources, so you can double check the accuracy of the reference material.</p>
<p>For the curious, here&rsquo;s the full research report below, that my agent crafted about rock-solid webhooks:</p>

<details>
  <summary>Click to view the full generated report on Webhooks</summary>
  <h2 id="webhook-implementation-best-practices-security-reliability-and-scalability">Webhook Implementation Best Practices: Security, Reliability, and Scalability</h2>
<h3 id="key-points"><strong>Key Points</strong></h3>
<ul>
<li><strong>Security is Paramount:</strong> Relying solely on obscure URLs is insufficient; implementation must include cryptographic signing (HMAC-SHA256) to ensure integrity and authenticity, alongside HTTPS for confidentiality.</li>
<li><strong>Reliability through Idempotency:</strong> Because webhooks typically guarantee &ldquo;at-least-once&rdquo; delivery, receivers must implement idempotency keys to safely handle duplicate requests without corrupting data state.</li>
<li><strong>Asynchronous Architecture:</strong> Decoupling ingestion from processing using message queues is critical for handling traffic bursts and preventing timeout failures at the ingress point.</li>
<li><strong>Failure Mitigation:</strong> Robust systems employ exponential backoff with jitter for retries to prevent &ldquo;thundering herd&rdquo; scenarios, utilizing Dead Letter Queues (DLQs) for effectively managing permanently failed messages.</li>
<li><strong>Traffic Management:</strong> Circuit breakers and rate limiting are essential to protect both the sender and receiver infrastructure from cascading failures during high-load events or outages.</li>
</ul>
<h3 id="introduction"><strong>Introduction</strong></h3>
<p>Webhooks represent the standard for event-driven communication between distributed systems, allowing platforms to notify downstream services of state changes in real-time. Unlike polling, which is resource-intensive and suffers from latency, webhooks enable a &ldquo;push&rdquo; model where data is transmitted immediately upon event occurrence. However, this architectural shift introduces significant challenges regarding security, reliability, and scalability. A production-grade webhook implementation requires a rigorous adherence to defensive design patterns to handle network partition, malicious actors, and extreme volume spikes.</p>
<p>This report synthesizes best practices across security verification, architectural decoupling, failure recovery, and payload design. It serves as a comprehensive guide for engineering teams aiming to build or consume resilient webhook systems that function reliably at scale.</p>
<h4 id="1-cryptographic-security-and-authentication"><strong>1. Cryptographic Security and Authentication</strong></h4>
<p>The public exposure of webhook endpoints makes them susceptible to impersonation, tampering, and man-in-the-middle attacks. Security must be implemented in layers, primarily focusing on transport security and payload verification.</p>
<h5 id="11-hmac-sha256-signature-verification"><strong>1.1 HMAC-SHA256 Signature Verification</strong></h5>
<p>The industry standard for authenticating webhook payloads is the Hash-based Message Authentication Code (HMAC) using the SHA-256 algorithm. This mechanism allows the receiver to verify that the payload was generated by the expected sender and has not been modified in transit <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQCXOGuJl7Dh-VhImsadFbEWJxadTB33mDMPL2zHVmbHLv1IfGzFfSEOPmNZolZ00UFxk13zMvSO9B2llkj2RvC8tHuUF3bSc8c9KRD4Ww0yIH_1EbuKPDj03sOz5Y0VsLzTJPPh1TVzK4n5l8kLgSZmrmFROXpNMvIxvs5rjDIr2lIYRLaEUpgcM6uqk=">1</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGapOwdbfMw2xiKf7LQTypi27arEInXlcwMSM0OvS9Irikt1PHqfjKo7eYX1-GSXVUnL1OZKvJPBglA0qm3gwyUNEteJbfdTzPs3rGU1u9KFaVgnecU31VCHI9sAoLW6216rF5E_DmHOhb7g2DP-SfhA7Nv6OxoS08HZEaK-XGw3LBF5NG7W8n4kww_Iw8j6DXuhqLF3g==">2</a>.</p>
<p><strong>Mechanism of Action:</strong></p>
<ol>
<li><strong>Shared Secret:</strong> A secret key is exchanged between the webhook provider and the consumer. This key is never transmitted over the network but is stored securely (e.g., in a Key Management Service) <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGapOwdbfMw2xiKf7LQTypi27arEInXlcwMSM0OvS9Irikt1PHqfjKo7eYX1-GSXVUnL1OZKvJPBglA0qm3gwyUNEteJbfdTzPs3rGU1u9KFaVgnecU31VCHI9sAoLW6216rF5E_DmHOhb7g2DP-SfhA7Nv6OxoS08HZEaK-XGw3LBF5NG7W8n4kww_Iw8j6DXuhqLF3g==">2</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHtLMqFzEPWWTqr57hgCAajV8f2Ij_c5UJeD5WwDVNpUV3KinmSkEGvoP7IlL0h77x5SOjddKsCURFSbuoHB9N0fMQEuZwA6vaxQ3RQb8C4dnCsp7Y0pcosupydpCz5fIJL0A-J8BadBDgNFkVG_TUiGKVx6nm4T8D5sPbuTg==">3</a>.</li>
<li><strong>Hashing:</strong> The provider computes a hash of the payload body using the shared secret and the SHA-256 algorithm.</li>
<li><strong>Transmission:</strong> This hash is included in the HTTP headers (e.g., X-Signature or X-Hub-Signature-256) sent with the POST request <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFS-EcH1LqHZM6UuNXeP-qitgt8WWv5rnN9tweCAL1n2Ek29RI_lGbpCGS6GSkPw2HsuOwmoxLEfSQB5p03WIGFpYueJRELK05i8aXPElmhgw9Wnxt9geuz9jBrOzWvRNiUC2Jtfh0vM0lO2-uzvqU=">4</a>.</li>
<li><strong>Verification:</strong> Upon receipt, the consumer independently computes the hash of the raw payload using their copy of the secret and compares it to the header value <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEXs9GPnNDFPvttHxuwH1fmRNaG7g0AEVP78LNYuD72y5prRsjxAWF1SJ7WgvtvtIU8dmVKOPBXtRWsieTrZQMM7OXct27gnZKRZAdfgsbQTPepbGNrXnoGvjH2hSqBMbdh04kEeOZmgZ3qyw==">5</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">6</a>.</li>
</ol>
<p><strong>Implementation Criticalities:</strong></p>
<ul>
<li><strong>Constant-Time Comparison:</strong> When comparing the calculated signature with the received signature, developers must use a constant-time string comparison function. Standard string comparison returns false as soon as a mismatch is found, which exposes the system to timing attacks where an attacker can deduce the signature character by character based on response time <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHtLMqFzEPWWTqr57hgCAajV8f2Ij_c5UJeD5WwDVNpUV3KinmSkEGvoP7IlL0h77x5SOjddKsCURFSbuoHB9N0fMQEuZwA6vaxQ3RQb8C4dnCsp7Y0pcosupydpCz5fIJL0A-J8BadBDgNFkVG_TUiGKVx6nm4T8D5sPbuTg==">3</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">6</a>.</li>
<li><strong>Raw Payload Access:</strong> Verification must be performed on the raw, unparsed request body. Frameworks that automatically parse JSON before verification can alter whitespace or field ordering, causing hash mismatches <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHtLMqFzEPWWTqr57hgCAajV8f2Ij_c5UJeD5WwDVNpUV3KinmSkEGvoP7IlL0h77x5SOjddKsCURFSbuoHB9N0fMQEuZwA6vaxQ3RQb8C4dnCsp7Y0pcosupydpCz5fIJL0A-J8BadBDgNFkVG_TUiGKVx6nm4T8D5sPbuTg==">3</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">6</a>.</li>
<li><strong>Key Rotation:</strong> Security best practices dictate the ability to rotate secrets without downtime. This is achieved by supporting multiple active keys during a transition period or using a Key-ID header to indicate which secret was used for signing <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQCXOGuJl7Dh-VhImsadFbEWJxadTB33mDMPL2zHVmbHLv1IfGzFfSEOPmNZolZ00UFxk13zMvSO9B2llkj2RvC8tHuUF3bSc8c9KRD4Ww0yIH_1EbuKPDj03sOz5Y0VsLzTJPPh1TVzK4n5l8kLgSZmrmFROXpNMvIxvs5rjDIr2lIYRLaEUpgcM6uqk=">1</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGapOwdbfMw2xiKf7LQTypi27arEInXlcwMSM0OvS9Irikt1PHqfjKo7eYX1-GSXVUnL1OZKvJPBglA0qm3gwyUNEteJbfdTzPs3rGU1u9KFaVgnecU31VCHI9sAoLW6216rF5E_DmHOhb7g2DP-SfhA7Nv6OxoS08HZEaK-XGw3LBF5NG7W8n4kww_Iw8j6DXuhqLF3g==">2</a>.</li>
</ul>
<h4 id="12-mutual-tls-mtls"><strong>1.2 Mutual TLS (mTLS)</strong></h4>
<p>While HTTPS ensures encryption in transit, Mutual TLS (mTLS) provides a higher level of authentication by requiring both the client (webhook sender) and the server (webhook receiver) to present valid x.509 certificates during the TLS handshake <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFS-EcH1LqHZM6UuNXeP-qitgt8WWv5rnN9tweCAL1n2Ek29RI_lGbpCGS6GSkPw2HsuOwmoxLEfSQB5p03WIGFpYueJRELK05i8aXPElmhgw9Wnxt9geuz9jBrOzWvRNiUC2Jtfh0vM0lO2-uzvqU=">4</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE3k1o-3NRkMcffQX8rtbkeOZtI6fj-vyPodgDETt437-jH_lOLx0vKahj92Vaxo28UE8xXjEAAigXWq6jNmbJysY_GhRMoQY9vw_gREej2hXsZ4atFoO-NPyd9pmC9cQkh3VEvU8MCvS8=">7</a>.</p>
<p><strong>Advantages and Trade-offs:</strong> mTLS aligns with Zero Trust security principles, ensuring that traffic is trusted in both directions at the transport layer before any application logic is invoked <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE3k1o-3NRkMcffQX8rtbkeOZtI6fj-vyPodgDETt437-jH_lOLx0vKahj92Vaxo28UE8xXjEAAigXWq6jNmbJysY_GhRMoQY9vw_gREej2hXsZ4atFoO-NPyd9pmC9cQkh3VEvU8MCvS8=">7</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXhbKv9jWGpppHz-I9OAOAkGHbvPQGssu-IzoCcTn7-AJvw_KRC0mtAWzp0ML2hS1UkoasxuhHfOCySlYdoFKmGGbOqw2usCXYiQfNz2wzBJ-46HpNCfh_OiX64LsO2iBrv-nH9tDfEZhTYUlPnjQwmolyARu3jSigFIC8KYykE04XjV8ccUWP_CLCz6tVC516k0WI2SfUEoFzX1uQhE6xJa4rDEq6JhURZuEt7rhKDNqURA==">8</a>. It effectively mitigates spoofing and man-in-the-middle attacks. However, it introduces significant operational complexity regarding certificate management, issuance, and rotation <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE3k1o-3NRkMcffQX8rtbkeOZtI6fj-vyPodgDETt437-jH_lOLx0vKahj92Vaxo28UE8xXjEAAigXWq6jNmbJysY_GhRMoQY9vw_gREej2hXsZ4atFoO-NPyd9pmC9cQkh3VEvU8MCvS8=">7</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXhbKv9jWGpppHz-I9OAOAkGHbvPQGssu-IzoCcTn7-AJvw_KRC0mtAWzp0ML2hS1UkoasxuhHfOCySlYdoFKmGGbOqw2usCXYiQfNz2wzBJ-46HpNCfh_OiX64LsO2iBrv-nH9tDfEZhTYUlPnjQwmolyARu3jSigFIC8KYykE04XjV8ccUWP_CLCz6tVC516k0WI2SfUEoFzX1uQhE6xJa4rDEq6JhURZuEt7rhKDNqURA==">8</a>. For many use cases, mTLS is considered overkill compared to HMAC, but it remains the gold standard for high-security environments like banking or healthcare <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXhbKv9jWGpppHz-I9OAOAkGHbvPQGssu-IzoCcTn7-AJvw_KRC0mtAWzp0ML2hS1UkoasxuhHfOCySlYdoFKmGGbOqw2usCXYiQfNz2wzBJ-46HpNCfh_OiX64LsO2iBrv-nH9tDfEZhTYUlPnjQwmolyARu3jSigFIC8KYykE04XjV8ccUWP_CLCz6tVC516k0WI2SfUEoFzX1uQhE6xJa4rDEq6JhURZuEt7rhKDNqURA==">8</a>.</p>
<h4 id="13-ip-allowlisting"><strong>1.3 IP Allowlisting</strong></h4>
<p>Restricting webhook traffic to a specific list of IP addresses (allowlisting) is a common defense-in-depth strategy. By blocking all traffic not originating from known provider IPs, the attack surface is reduced <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEDIwllRPtSE_Pf62YeXgIYgsDEfs-1wHuv9ezOKwFYSaEc39N4e_yAOhbtRX1t-lLnt_VqbdnEsbq1uSII6uRxqit-ZihDjM4StrhiNYPplF_yDLSKzglCmQg_JtAsbsmocQQU6WsYQo0kdLodcetTYvUPbQWjEMuZcovNsV0IvMRm4JFcWykx9BIBvrIMKg999w==">9</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGImss29WCJrZ-7A1qRvtpSXx2770lcn-FKzrohjNSvahorZqWNKElMcdM8ncv0_woeJHwVb1-xgukNmufkutg13vEv6wXuSWlpA5Xez7xFPYWUO9B1_c1B645Bj1Tfjmr8KEQr8A==">10</a>.</p>
<p><strong>Limitations in Modern Architectures:</strong> IP allowlisting is increasingly difficult to maintain in cloud-native environments where providers use dynamic IP ranges or serverless infrastructure <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFYYHzohwba_5v9CJiFs8mD1iz9fbhE8MtwfSvmrUpiSIuWRvl2kvOSLUz9Uvjk6_5n-fLTAnIyoWJe2zh6vCb1i0m2bmHtT2vlK_jU2o-aPA0hlfcOtinI7exwqNXqj9Vrv1Oq6--c0TPo-NGY23eKvhemf-YeZ6UQWTITLFB1oWHphtQzzR2dfuIB4IYT3URzUbF8VVUosF8=">11</a>. It creates a maintenance burden where the consumer must manually update firewall rules whenever the provider expands their infrastructure <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHyAw6S_aOVlsVoLFQg0ZMCQlWmrBgVHp4dBnn2xttREglexTIahmODOAstaUnlwsNIP87xlAcCBSasuOixIkwZ6P5b6tzO9rqx4rahyPJ98wCyFn9P_A3mpjCOu7wNm_xYBANICeyhNA==">12</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH1xca3J3HdA9cCG1LqCARxi2f2drvrB3kvAEkgwonEY73ciX1Lfc37H71xRSwdAwybTc7UIK6pLHVBmlgaa5oQG-hkozuAkTN_ESLLLgMFL3uXCe_YKsU4QDCfxWE9V8YUG8_9iVClMHZsmwa0Ny7ZUUTFq_7IPBGSW85ourPVJ9I=">13</a>. Consequently, IP allowlisting should be treated as a supplementary measure rather than a primary authentication method <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHtLMqFzEPWWTqr57hgCAajV8f2Ij_c5UJeD5WwDVNpUV3KinmSkEGvoP7IlL0h77x5SOjddKsCURFSbuoHB9N0fMQEuZwA6vaxQ3RQb8C4dnCsp7Y0pcosupydpCz5fIJL0A-J8BadBDgNFkVG_TUiGKVx6nm4T8D5sPbuTg==">3</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH1xca3J3HdA9cCG1LqCARxi2f2drvrB3kvAEkgwonEY73ciX1Lfc37H71xRSwdAwybTc7UIK6pLHVBmlgaa5oQG-hkozuAkTN_ESLLLgMFL3uXCe_YKsU4QDCfxWE9V8YUG8_9iVClMHZsmwa0Ny7ZUUTFq_7IPBGSW85ourPVJ9I=">13</a>.</p>
<h4 id="14-preventing-replay-attacks"><strong>1.4 Preventing Replay Attacks</strong></h4>
<p>A replay attack occurs when an attacker intercepts a valid, signed webhook request and resends it to the endpoint to duplicate an action (e.g., forcing a second payment).</p>
<p><strong>Timestamp Validation:</strong> To prevent this, the signature header should include a timestamp. The receiver verifies that the timestamp is within a strictly defined tolerance window (e.g., 5 minutes) relative to the system time <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">6</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mQdSQzonHQsRQLG1x2iQbAc14h1b0QyfDZPE1MsFLTlnRODkc6cJqhFJfn5sTtonCXscipnJQhEOtW1TgQoZt9bJ5wG-o8XJY9hWEw2iHJOXWDuFMxbviQLx5h-_dgoXF-Op">14</a>. If the request is too old, it is rejected, even if the signature is valid. Including the timestamp in the signed payload ensures the attacker cannot modify the time to bypass the check <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mQdSQzonHQsRQLG1x2iQbAc14h1b0QyfDZPE1MsFLTlnRODkc6cJqhFJfn5sTtonCXscipnJQhEOtW1TgQoZt9bJ5wG-o8XJY9hWEw2iHJOXWDuFMxbviQLx5h-_dgoXF-Op">14</a>.</p>
<p><strong>Nonce Implementation:</strong> For stronger protection, a unique &ldquo;nonce&rdquo; (number used once) or unique request ID can be included. The receiver stores processed nonces in a fast lookup store (like Redis) with a Time-To-Live (TTL) matching the replay window. If a nonce is seen a second time, the request is rejected <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">6</a>.</p>
<h3 id="2-reliability-and-data-integrity"><strong>2. Reliability and Data Integrity</strong></h3>
<p>Distributed systems cannot guarantee &ldquo;exactly-once&rdquo; delivery due to network acknowledgments potentially failing after processing. Therefore, webhook systems almost universally operate on an &ldquo;at-least-once&rdquo; delivery model, necessitating robust handling of duplicate events.</p>
<h4 id="21-idempotency-implementation"><strong>2.1 Idempotency Implementation</strong></h4>
<p>Idempotency ensures that performing the same operation multiple times produces the same result as performing it once. This is the primary defense against data corruption caused by webhook retries <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQfVAEpbMLruPbjNGZ738eh_ylZHWHPnu_qZm9XTLHB-AT0BmDjUV_H1Lv00QMDQhFo9DgyxKxPUtoHpGCr0y7XlujSMPRVNqbFaRcmSCOGLV78IAM-ewJFRv_ttzj2JOp_tgD2udxcyAIsAUbcLhsS1uYoNKoJkI=">15</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEHXKe8AnCoBNUic4_dIw3dZ5D6GIcUTosndmVu1nzaiSfbBhtV04q7oMzc4l71nWdcRfuExdy1qe2cHZj_2E8QGFjwSWWlQthKKPP3YDgs9Wm5ZiXPAS6z7rAhkvllqe6Uw73rz9UGA1IPPZ_-RI4h124D0AocAIjiY798wYZn5_k0U_K1qAcdSG9jxHTwuFcaEEUz9g==">16</a>.</p>
<p><strong>Idempotency Keys:</strong> Providers should include a unique identifier (Idempotency Key or event_id) in the webhook headers or payload <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEHXKe8AnCoBNUic4_dIw3dZ5D6GIcUTosndmVu1nzaiSfbBhtV04q7oMzc4l71nWdcRfuExdy1qe2cHZj_2E8QGFjwSWWlQthKKPP3YDgs9Wm5ZiXPAS6z7rAhkvllqe6Uw73rz9UGA1IPPZ_-RI4h124D0AocAIjiY798wYZn5_k0U_K1qAcdSG9jxHTwuFcaEEUz9g==">16</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEATF-8ojdZKpkXPPqDJmcexidA8Fo4g8M1737OwRPdyZbSoXi0RKkhctGTp62J5PReRU1dihQLC-Rr9o32-K-Gy2KjqBWlMjZa1hW2qWQtCVPYiNMXB-p1jRIfT9xLvPQL84kZg3YwHlxDck22cDUqRP94Uc7d3W5HuZNc2A87PCmU8peDNqbza0zPKZmTQuFZ9gaJsw==">17</a>. The receiver uses this key to lock processing for that specific event.</p>
<ul>
<li><strong>Deduplication Store:</strong> A fast, atomic store (e.g., Redis) checks if the key has been processed. Using atomic operations like SETNX (Set if Not Exists) prevents race conditions where two parallel requests for the same event might both proceed <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEHXKe8AnCoBNUic4_dIw3dZ5D6GIcUTosndmVu1nzaiSfbBhtV04q7oMzc4l71nWdcRfuExdy1qe2cHZj_2E8QGFjwSWWlQthKKPP3YDgs9Wm5ZiXPAS6z7rAhkvllqe6Uw73rz9UGA1IPPZ_-RI4h124D0AocAIjiY798wYZn5_k0U_K1qAcdSG9jxHTwuFcaEEUz9g==">16</a>.</li>
<li><strong>Retention Window:</strong> The keys should be stored for a duration exceeding the maximum retry window of the provider (typically 24 to 72 hours) <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHMukyFGeJn3lzizl34s01hqUqLH39KV4C3rqM6KdUEdBXPX9w1RSuEFAKZT_UX0C1NZu8KL_-4t3UzO0_cg69Nb_JI_u4dk8GMpFAhLqOOqPO8FuD3hgDsb45rLo0v8o1ZlOeLBpF2ZcZ3KXE7CPR-5ErLgSbfb9k8mPvJXOnpxUg1xtJoa0ynC8XFF0c=">18</a>.</li>
<li><strong>Transactional Upserts:</strong> In database operations, using &ldquo;upsert&rdquo; logic (update if exists, insert if new) based on the unique event ID ensures consistency at the database level <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEHXKe8AnCoBNUic4_dIw3dZ5D6GIcUTosndmVu1nzaiSfbBhtV04q7oMzc4l71nWdcRfuExdy1qe2cHZj_2E8QGFjwSWWlQthKKPP3YDgs9Wm5ZiXPAS6z7rAhkvllqe6Uw73rz9UGA1IPPZ_-RI4h124D0AocAIjiY798wYZn5_k0U_K1qAcdSG9jxHTwuFcaEEUz9g==">16</a>.</li>
</ul>
<h4 id="22-asynchronous-processing-architectures"><strong>2.2 Asynchronous Processing Architectures</strong></h4>
<p>Synchronous processing of webhooks—where the receiver executes business logic before returning an HTTP response—is a major anti-pattern. It couples the provider&rsquo;s availability to the consumer&rsquo;s processing speed and risks timeouts <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQfVAEpbMLruPbjNGZ738eh_ylZHWHPnu_qZm9XTLHB-AT0BmDjUV_H1Lv00QMDQhFo9DgyxKxPUtoHpGCr0y7XlujSMPRVNqbFaRcmSCOGLV78IAM-ewJFRv_ttzj2JOp_tgD2udxcyAIsAUbcLhsS1uYoNKoJkI=">15</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEHXKe8AnCoBNUic4_dIw3dZ5D6GIcUTosndmVu1nzaiSfbBhtV04q7oMzc4l71nWdcRfuExdy1qe2cHZj_2E8QGFjwSWWlQthKKPP3YDgs9Wm5ZiXPAS6z7rAhkvllqe6Uw73rz9UGA1IPPZ_-RI4h124D0AocAIjiY798wYZn5_k0U_K1qAcdSG9jxHTwuFcaEEUz9g==">16</a>.</p>
<p><strong>Queue-Based Decoupling:</strong> The recommended architecture involves an ingestion layer that does nothing but authenticate the request, push the payload to a message queue (e.g., RabbitMQ, Kafka, SQS), and immediately return a 202 Accepted status <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHdp9hSU08oYMf-4GdNFGBYU4v4d93LvZ1d_ICLNnJcPAG2fUTOKHohQ-kOnJsaA6Teu2FI8Ut30grgFQBpXeaydSjx9bUWXZoU_hSLg_aL73uSS9CsVmK0LFDKAtq3YG4D31h1Kuszk39-Lk3nu09_NajWdBOTQy-_M8QxfRrEU9fL6K4WYNU28bRd_69TmlowBm2krMq_IWLW-qzdtaPgV3zuPDnB1w==">19</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH0JVPUOmptuBYlB8-wJwyjeuTAVRVyUb223KovQt8MsM-mlOtej6D_kcchgaC2ZwcjcEx0wq13bm76jEk23wIs0we5HpQ0QAgfz9A2FMmhmzNDMGbHA_jg0dMemVpSjrN_B-dQh6lKCPSh52kD7ND2Z6JdPrzXn_zdyqD3Tv4NZzOIHCfNK5myi6e-LOOUybc=">20</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH55diJUzcJR_u2AWR3Mr6S8HabBX1seyCwwOzAXTTU96fhqNP4RUhs06bklN1nNYw8eTQQd6K9c9NXUVwJ6kJN4y1CD8PMECf1LHUmz7X04825mo3sNLM72Er8DgB0tZ7SSlZl1-y2knwdSj8lxRwVWilPCBC9sGj3LHy_KBcphb5G6bcrehvsRDvgmioQzQepSjQOWAFn">21</a>.</p>
<ul>
<li><strong>Benefits:</strong> This ensures that the ingestion layer can handle high throughput without waiting for slow downstream processes (e.g., generating PDFs, sending emails) <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQED662YLvMnAViKnlF958nxB2BmJpS8SQlmVbMuE3Ko2pNQr0b3kaD7B_hyk1HUebgY3IcCs7tGCNRnInAdhxH-LtRvP6C-8byHujzYJChaayy4NZ_BaC3c4VkXU2dSYKuJ">22</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFd_CxUJPKeVMz1F02SqlVQjKBwFSscQ8mtFX755DcIRg6EU2j5-mjqOJu8hflle4-VnsqJbFMLbfTmn5fZUWuoq6RBBxRAlRKJWTYK0i9j9b7_ZTFlbVCRcc2DLxpvESUfrCiUPBj_a01pRXCaQ3lSo4v-5yuecvnWx6GAnnWg8M4vN4G1PnrSbZL-XcgBmsFqINYgUuI0b3yQR_BgMITXm7G1BCZ2Mm4=">23</a>.</li>
<li><strong>Worker Pattern:</strong> Background workers pull messages from the queue to process them. If a worker fails, the message remains in the queue or is moved to a retry queue, ensuring no data is lost during application crashes <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH0JVPUOmptuBYlB8-wJwyjeuTAVRVyUb223KovQt8MsM-mlOtej6D_kcchgaC2ZwcjcEx0wq13bm76jEk23wIs0we5HpQ0QAgfz9A2FMmhmzNDMGbHA_jg0dMemVpSjrN_B-dQh6lKCPSh52kD7ND2Z6JdPrzXn_zdyqD3Tv4NZzOIHCfNK5myi6e-LOOUybc=">20</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHJ3oedQbVxZag1bGXe54yIdze9Q9z8q0IPuWb3dPI47uWDHpqJz9cHmjP5-hZ6XYoQ8R-jYwG0hwIahcK4RbJwtYnyZElCsPNpqm4y1nzN-ClzqQ8DV2Fn9v69cFHr-NZnLfaKp7gy1fnIyHCorG7A-TaoWdCCR-6B5Y4CS0vP--U69OqbFvNGJnQB8klIbl36rFWVvvGG6A==">24</a>.</li>
<li><strong>Buffering:</strong> This architecture acts as a buffer (shock absorber) during traffic spikes, allowing the system to &ldquo;hold the load&rdquo; and process it at a manageable rate rather than crashing the web server <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFd_CxUJPKeVMz1F02SqlVQjKBwFSscQ8mtFX755DcIRg6EU2j5-mjqOJu8hflle4-VnsqJbFMLbfTmn5fZUWuoq6RBBxRAlRKJWTYK0i9j9b7_ZTFlbVCRcc2DLxpvESUfrCiUPBj_a01pRXCaQ3lSo4v-5yuecvnWx6GAnnWg8M4vN4G1PnrSbZL-XcgBmsFqINYgUuI0b3yQR_BgMITXm7G1BCZ2Mm4=">23</a>.</li>
</ul>
<h3 id="3-failure-handling-and-recovery"><strong>3. Failure Handling and Recovery</strong></h3>
<p>Failures in webhook delivery are inevitable due to network blips, downtime, or bugs. A robust system must distinguish between transient and permanent failures and handle each appropriately.</p>
<h5 id="31-retry-strategies-exponential-backoff-and-jitter"><strong>3.1 Retry Strategies: Exponential Backoff and Jitter</strong></h5>
<p>When a webhook delivery fails (e.g., receiver returns 500 or times out), the provider must retry. However, immediate retries can worsen the issue, especially if the receiver is overloaded.</p>
<p><strong>Exponential Backoff:</strong> This algorithm increases the wait time between retries exponentially (e.g., 1s, 2s, 4s, 8s). This gives the failing system &ldquo;breathing room&rdquo; to recover <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkjQjNpTsb8JZ5qe59IFdmmAjGw8COOq_1pGnrUe37t1NDdlwblViRj7cSp7gccbNFGm1n5EQ1r6szF-x9YqTl4P5yKbnDbwfON9RSKjv3IwjZeKTSgVWmATqBkax3VdITfVgQKQpQD8GePx0=">25</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF99_r_zUtU_jYwFqB_uQJoJXExrO3kKFls8vi9mILvSgAUmdwy6FH_pE_OR796JSUEUJ-tYDqUfYDkStQBVIv7gcPbezN4RfgTSTIUFP-rXh24CgzRm3yr4JFMeqO4WX9_i2cUz6_765Rj7HRrVEEO7tWyRA==">26</a>.</p>
<ul>
<li><strong>Formula:</strong> 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><mi>e</mi><mi>l</mi><mi>a</mi><mi>y</mi><mo>=</mo><mi>B</mi><mi>a</mi><mi>s</mi><mi>e</mi><mo>×</mo><msup><mn>2</mn><mrow><mi>A</mi><mi>t</mi><mi>t</mi><mi>e</mi><mi>m</mi><mi>p</mi><mi>t</mi></mrow></msup></mrow><annotation encoding="application/x-tex">Delay = Base \times 2^{Attempt}</annotation></semantics></math></span>

 <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkjQjNpTsb8JZ5qe59IFdmmAjGw8COOq_1pGnrUe37t1NDdlwblViRj7cSp7gccbNFGm1n5EQ1r6szF-x9YqTl4P5yKbnDbwfON9RSKjv3IwjZeKTSgVWmATqBkax3VdITfVgQKQpQD8GePx0=">25</a>.</li>
<li><strong>Capping:</strong> A maximum delay (e.g., 1 hour) prevents retry intervals from becoming unreasonably long <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkjQjNpTsb8JZ5qe59IFdmmAjGw8COOq_1pGnrUe37t1NDdlwblViRj7cSp7gccbNFGm1n5EQ1r6szF-x9YqTl4P5yKbnDbwfON9RSKjv3IwjZeKTSgVWmATqBkax3VdITfVgQKQpQD8GePx0=">25</a>.</li>
</ul>
<p><strong>Jitter:</strong> Exponential backoff alone can lead to the &ldquo;Thundering Herd&rdquo; problem, where multiple failed webhooks retry at the exact same synchronized times, creating repeated spikes of traffic. &ldquo;Jitter&rdquo; adds randomness to the backoff interval to desynchronize these retries <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkjQjNpTsb8JZ5qe59IFdmmAjGw8COOq_1pGnrUe37t1NDdlwblViRj7cSp7gccbNFGm1n5EQ1r6szF-x9YqTl4P5yKbnDbwfON9RSKjv3IwjZeKTSgVWmATqBkax3VdITfVgQKQpQD8GePx0=">25</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGrBXavT7Taw429hZQnfpdVUgMSqV0qPk3J1Kr1FYj9RI1REkA6gaHCgEcJpIE3pUUB5LOxm1mWgiZMr7bT1JsOLoNm7PO4TYhMMyLwJ0_HD95w9WrxiqJoLJVJJ4weqq7Xfaa1dOHz1gjxeHd8eNPeQRXEHsJFi6DwWKjldZa99ugI7ObRlXaXiAtlVPdAiY2I8L8pual1oHwnmbgajvVoMq1hSwplMf0ue9Y=">27</a>.</p>
<ul>
<li><strong>Full Jitter:</strong> 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><mi>e</mi><mi>l</mi><mi>a</mi><mi>y</mi><mo>=</mo><mi>r</mi><mi>a</mi><mi>n</mi><mi>d</mi><mi>o</mi><mi>m</mi><mo stretchy="false">(</mo><mn>0</mn><mo separator="true">,</mo><mi>B</mi><mi>a</mi><mi>s</mi><mi>e</mi><mo>×</mo><msup><mn>2</mn><mrow><mi>A</mi><mi>t</mi><mi>t</mi><mi>e</mi><mi>m</mi><mi>p</mi><mi>t</mi></mrow></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">Delay = random(0, Base \times 2^{Attempt})</annotation></semantics></math></span>

 is highly effective at spreading load <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHMukyFGeJn3lzizl34s01hqUqLH39KV4C3rqM6KdUEdBXPX9w1RSuEFAKZT_UX0C1NZu8KL_-4t3UzO0_cg69Nb_JI_u4dk8GMpFAhLqOOqPO8FuD3hgDsb45rLo0v8o1ZlOeLBpF2ZcZ3KXE7CPR-5ErLgSbfb9k8mPvJXOnpxUg1xtJoa0ynC8XFF0c=">18</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkjQjNpTsb8JZ5qe59IFdmmAjGw8COOq_1pGnrUe37t1NDdlwblViRj7cSp7gccbNFGm1n5EQ1r6szF-x9YqTl4P5yKbnDbwfON9RSKjv3IwjZeKTSgVWmATqBkax3VdITfVgQKQpQD8GePx0=">25</a>.</li>
</ul>
<h4 id="32-dead-letter-queues-dlq"><strong>3.2 Dead Letter Queues (DLQ)</strong></h4>
<p>If a message fails to deliver after all retry attempts are exhausted, it should not be discarded. Instead, it must be moved to a Dead Letter Queue (DLQ) <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHdp9hSU08oYMf-4GdNFGBYU4v4d93LvZ1d_ICLNnJcPAG2fUTOKHohQ-kOnJsaA6Teu2FI8Ut30grgFQBpXeaydSjx9bUWXZoU_hSLg_aL73uSS9CsVmK0LFDKAtq3YG4D31h1Kuszk39-Lk3nu09_NajWdBOTQy-_M8QxfRrEU9fL6K4WYNU28bRd_69TmlowBm2krMq_IWLW-qzdtaPgV3zuPDnB1w==">19</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF99_r_zUtU_jYwFqB_uQJoJXExrO3kKFls8vi9mILvSgAUmdwy6FH_pE_OR796JSUEUJ-tYDqUfYDkStQBVIv7gcPbezN4RfgTSTIUFP-rXh24CgzRm3yr4JFMeqO4WX9_i2cUz6_765Rj7HRrVEEO7tWyRA==">26</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEW2f40SZCjfELqmNW1j9NyGNsCNsip2VIO51aLp1fUFClxVo6-KLqVo1OfM-Bevq9PGU_Kf_38maQ4Jmrdxqg7x_H5FmQ3dlUczpv5puiR6LdHG7CPYhZ14eWfHt7G8LHnwfjk_CCMF64KmU9bR5uoSNqShdc6ddPcjZW8_eOBw7M=">28</a>.</p>
<ul>
<li><strong>Purpose:</strong> The DLQ acts as a holding area for &ldquo;poison messages&rdquo; or permanently failed deliveries. This prevents the retry queue from being clogged with unprocessable events <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEATF-8ojdZKpkXPPqDJmcexidA8Fo4g8M1737OwRPdyZbSoXi0RKkhctGTp62J5PReRU1dihQLC-Rr9o32-K-Gy2KjqBWlMjZa1hW2qWQtCVPYiNMXB-p1jRIfT9xLvPQL84kZg3YwHlxDck22cDUqRP94Uc7d3W5HuZNc2A87PCmU8peDNqbza0zPKZmTQuFZ9gaJsw==">17</a>.</li>
<li><strong>Management:</strong> Systems must provide tooling to inspect DLQ messages, determine the root cause (e.g., bug in consumer code), and &ldquo;redrive&rdquo; (replay) them once the issue is fixed <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHMukyFGeJn3lzizl34s01hqUqLH39KV4C3rqM6KdUEdBXPX9w1RSuEFAKZT_UX0C1NZu8KL_-4t3UzO0_cg69Nb_JI_u4dk8GMpFAhLqOOqPO8FuD3hgDsb45rLo0v8o1ZlOeLBpF2ZcZ3KXE7CPR-5ErLgSbfb9k8mPvJXOnpxUg1xtJoa0ynC8XFF0c=">18</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHx0DNAVU9VSUpsBSRtjwACgynyVes1j87NAFlezz2pJ6BkCNAnlKrz1tHo0I793oZ8cfTFVct_0ueqFEz3OtLjiWcLCX90oM1Ux_BZuWr7l6PzvEaImxxrTqIy9M5NMn_RDEG3Gk-wfJHvdOF8XC-u1A==">29</a>.</li>
<li><strong>Alerting:</strong> The size of the DLQ is a critical metric; growing DLQ depth should trigger alerts for manual intervention <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEATF-8ojdZKpkXPPqDJmcexidA8Fo4g8M1737OwRPdyZbSoXi0RKkhctGTp62J5PReRU1dihQLC-Rr9o32-K-Gy2KjqBWlMjZa1hW2qWQtCVPYiNMXB-p1jRIfT9xLvPQL84kZg3YwHlxDck22cDUqRP94Uc7d3W5HuZNc2A87PCmU8peDNqbza0zPKZmTQuFZ9gaJsw==">17</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHdp9hSU08oYMf-4GdNFGBYU4v4d93LvZ1d_ICLNnJcPAG2fUTOKHohQ-kOnJsaA6Teu2FI8Ut30grgFQBpXeaydSjx9bUWXZoU_hSLg_aL73uSS9CsVmK0LFDKAtq3YG4D31h1Kuszk39-Lk3nu09_NajWdBOTQy-_M8QxfRrEU9fL6K4WYNU28bRd_69TmlowBm2krMq_IWLW-qzdtaPgV3zuPDnB1w==">19</a>.</li>
</ul>
<h5 id="33-circuit-breaker-pattern"><strong>3.3 Circuit Breaker Pattern</strong></h5>
<p>While retries handle individual message failures, the Circuit Breaker pattern protects the entire ecosystem from total collapse during prolonged outages <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEii1RdCo-T6eB4tvb4eqOGUFXXz86BAwvINlXVzeqixPAe4JoWDgnCEBSXVxkmYsG2grSzKTsx0HES6tGxKTNLiu4QWsF8F_KirhZWy4T34iyby3QYriXlFhsUBmvP33AQ5xkmqsPGex3VIzZKEhLDJDWx491NGX7SysFplvIamdaIGyhvkTcQnifz1eRmdo5qs1BpvdiRMmCin3gY6-IZcf3KJA==">30</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF4clgaz1Awh_GB47zB8l4w-lRCthsTIWD6SuBJwCTCHaGi9JO0Ap-1rMelQreT6KjtzTWqtapMz6xhm_H3pX7tKtALVd6i8YVM8lV0vuXQuO7ttX5E3ySHcZQPI0sncecV5S_rsxYmhMjxKUkkqxraEdedxVDbgUZvvWCUj0Lcvg==">31</a>.</p>
<p><strong>Functionality:</strong> If a specific endpoint fails a significant percentage of requests (e.g., 100 failures in 60 seconds), the circuit breaker &ldquo;trips&rdquo; to an <strong>Open</strong> state.</p>
<ul>
<li><strong>Open State:</strong> Delivery is paused entirely for that endpoint. The provider stops wasting resources trying to send requests that will likely fail <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF4clgaz1Awh_GB47zB8l4w-lRCthsTIWD6SuBJwCTCHaGi9JO0Ap-1rMelQreT6KjtzTWqtapMz6xhm_H3pX7tKtALVd6i8YVM8lV0vuXQuO7ttX5E3ySHcZQPI0sncecV5S_rsxYmhMjxKUkkqxraEdedxVDbgUZvvWCUj0Lcvg==">31</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF3SixGEwLnJSSDKs0a8OOppywsIW7q2J8aZjaO7L1PLb6bH5CAnbroqLQwsBO0dZZsGDOtgF4aLdZLQeJX-fIw-RqvbES0LRDmTCZyGqvhBk-TVkS3P-XjxaDWdiptgeVjhOTOkUN_0lgqzkZk_G8ObsMcZlqoMpdvykuzrSuRkMSFwJrzsv-Za74qFDK3Y4Ko">32</a>.</li>
<li><strong>Half-Open State:</strong> After a cooldown period, the system allows a limited number of &ldquo;test&rdquo; requests. If these succeed, the circuit closes and normal flow resumes. If they fail, it re-opens <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF4clgaz1Awh_GB47zB8l4w-lRCthsTIWD6SuBJwCTCHaGi9JO0Ap-1rMelQreT6KjtzTWqtapMz6xhm_H3pX7tKtALVd6i8YVM8lV0vuXQuO7ttX5E3ySHcZQPI0sncecV5S_rsxYmhMjxKUkkqxraEdedxVDbgUZvvWCUj0Lcvg==">31</a>.</li>
<li><strong>Distributed State:</strong> In large systems, the state of the circuit breaker is often managed in a distributed store like Etcd or Redis to synchronize awareness across all delivery nodes <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHnX_dTTnINzT7X8e5hlMotKAfsSndIjiQk6jQzsN796QPbtzgqxv0KSq6614IFuC9MAo0fT6uUi2CDRsw6G53zjPoVha2es6TiRmz4pIDeoSqk0fWjovhwbbG5xLVvm5-w1FC_vXwRiop2PfXAg089Czp7PKLpL6MyoDQT5Js3vAPE0VXfSxsBgTC6ytO63A==">33</a>.</li>
</ul>
<h3 id="4-scalability-and-payload-design"><strong>4. Scalability and Payload Design</strong></h3>
<p>As systems grow, the volume and complexity of webhook events increase. Proper design choices in payloads and traffic management are essential for long-term maintainability.</p>
<h4 id="41-handling-high-volume-bursts"><strong>4.1 Handling High-Volume Bursts</strong></h4>
<p>Webhook traffic is rarely uniform; it is &ldquo;bursty&rdquo; by nature (e.g., bulk updates triggering thousands of events) <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFd_CxUJPKeVMz1F02SqlVQjKBwFSscQ8mtFX755DcIRg6EU2j5-mjqOJu8hflle4-VnsqJbFMLbfTmn5fZUWuoq6RBBxRAlRKJWTYK0i9j9b7_ZTFlbVCRcc2DLxpvESUfrCiUPBj_a01pRXCaQ3lSo4v-5yuecvnWx6GAnnWg8M4vN4G1PnrSbZL-XcgBmsFqINYgUuI0b3yQR_BgMITXm7G1BCZ2Mm4=">23</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGlXCpA03cjm2HhSB_1tZBCddlHw7t9zrzGIWVyeXRPhmyshdf1u7gelYqt4L3TVML4ucYYal6EABFM4XOGo8fz22QJzpFqFiSJFwsJsCTx1dCeqo0uBGSUyYFCE8JHAwxUNXmLt6cLY6hv0FYzRnFRFHFpj2_4l1Fv9HLO8X0Mzlc0XAdVF_GB5r9ViToyKWk=">34</a>.</p>
<p><strong>Rate Limiting:</strong> Providers should enforce rate limits on outgoing webhooks to prevent overwhelming consumer endpoints. This smoothens traffic spikes into a consistent stream <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEZ-84ZM44XCpM1vFVUNfrYG0zPZ6SeH4Y4G_nIIuv2XBee_SscnA4KYHIYW0g3CGFzDadJjeR0BpGnOwWpreUXbvbMWdcWDvObDBqUN-mKwKQMV8DHKCYKtBhcz4Ex5nSeg10PQzabmIIMqEfNMxhFt-CAL-1yOA==">35</a>. If the limit is reached, requests are throttled or queued rather than dropped <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEZ-84ZM44XCpM1vFVUNfrYG0zPZ6SeH4Y4G_nIIuv2XBee_SscnA4KYHIYW0g3CGFzDadJjeR0BpGnOwWpreUXbvbMWdcWDvObDBqUN-mKwKQMV8DHKCYKtBhcz4Ex5nSeg10PQzabmIIMqEfNMxhFt-CAL-1yOA==">35</a>.</p>
<p><strong>Buffering:</strong> For self-hosted solutions, an intermediate buffering layer (like NGINX or a lightweight &ldquo;Holding the Load&rdquo; service) can accept connections rapidly and persist requests to storage before they reach the heavier application logic <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFd_CxUJPKeVMz1F02SqlVQjKBwFSscQ8mtFX755DcIRg6EU2j5-mjqOJu8hflle4-VnsqJbFMLbfTmn5fZUWuoq6RBBxRAlRKJWTYK0i9j9b7_ZTFlbVCRcc2DLxpvESUfrCiUPBj_a01pRXCaQ3lSo4v-5yuecvnWx6GAnnWg8M4vN4G1PnrSbZL-XcgBmsFqINYgUuI0b3yQR_BgMITXm7G1BCZ2Mm4=">23</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGi86SD96fW-xFK_X84I9lPylpM6VBZGAFibU34NUtVmx1LcRW_HLP4fOYtJv2_vNff25pqSYwAq09S2tH4W6kCBXrao2NytEeUU0voKlbGnJfgslr823r8QuWJvJj3FPv3qh8i9Xwwx1VcRZbWiwCnjYdbQj-tVOgPkWIvrew8cQShYszht98=">36</a>.</p>
<h4 id="42-payload-design-fat-vs-thin-events"><strong>4.2 Payload Design: Fat vs. Thin Events</strong></h4>
<p>The content of the webhook payload involves a trade-off between efficiency and coupling.</p>
<ul>
<li><strong>Fat Payloads (Event-Carried State Transfer):</strong> The payload contains the full resource state (e.g., the complete Order object).
<ul>
<li><em>Pros:</em> Decouples systems; the consumer doesn&rsquo;t need to call back to the API to get data <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHKAYtR111JaUonVfus71SW3CQhTGuLaDlWY4AUaUzUzmLsqVmLSHvk32ut_5dkYafGZDCGL7ci6IFm4zM4TB160dmZLb7vILSvAOb99uHcarUrBEtL_ixdCugN60yQge0LND250y0z0JZXJMA=">37</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEurOYKLl9vW8_iQij2gRKetKr5a55aofIDB6vAl2O4TRlpJGiu4sBVeyq1l2RglZ0GgTjjoEp9Ig_MPyP5kdg7zbBA2jKLcn4-n-fczZkQw5joT-5kkDiJZfXjZz2KENyZ4XiF2PmJ-Cr99LHk">38</a>.</li>
<li><em>Cons:</em> Can leak sensitive data if not carefully filtered; payloads can become large (bloat); data might be stale by the time it is processed <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHKAYtR111JaUonVfus71SW3CQhTGuLaDlWY4AUaUzUzmLsqVmLSHvk32ut_5dkYafGZDCGL7ci6IFm4zM4TB160dmZLb7vILSvAOb99uHcarUrBEtL_ixdCugN60yQge0LND250y0z0JZXJMA=">37</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHJ0JCD1woL-NaTbyw-j7PlMbtwFt8RV_ZHv7G1aloJ4-ZUHNSRId0zvqSgnaI6Ec0QIhOiRuSz_YqX7cTbeOVZpSKllcWSrmdgnqpvVGtlPdGCVZ2uN5Of">39</a>.</li>
</ul>
</li>
<li><strong>Thin Payloads (Event Notification):</strong> The payload contains minimal data, typically just the event type and resource ID (e.g., {&ldquo;event&rdquo;: &ldquo;order.created&rdquo;, &ldquo;id&rdquo;: &ldquo;123&rdquo;}).
<ul>
<li><em>Pros:</em> Secure (no sensitive data in payload); ensures consumer fetches the latest state <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH7IgdgnjAYn56-UBWDUh9NHS55EnqLdOmTTx2JtV41oh7Wb7TjWnjHmNS-qkW3fAjQyvGFwbmCFyfAon-Lp7X1xJxaZAy9eHfFlEAXO9K3AJQ0U02vMI-YRUUpMK7Eqa5Q">40</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFD9i0zWR3HkaI2O4KoryMXqqgfE4NbOWvFDZM_hz0JjFAJXvi8wbVUDXbusd11PlzqseeNcBpFrj-L827YVMqnifjpnPyWo0lYQ71KbDn-gv-hfbN97e7oQTgg-2QikZzqNvujeXzv-3UsFfqNdeKKKSiM6qSsDQ==">41</a>.</li>
<li><em>Cons:</em> Increases API load due to &ldquo;callback&rdquo; traffic (consumer receiving the event immediately calls the API to get details) <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHKAYtR111JaUonVfus71SW3CQhTGuLaDlWY4AUaUzUzmLsqVmLSHvk32ut_5dkYafGZDCGL7ci6IFm4zM4TB160dmZLb7vILSvAOb99uHcarUrBEtL_ixdCugN60yQge0LND250y0z0JZXJMA=">37</a>.</li>
</ul>
</li>
<li><strong>Best Practice:</strong> Many systems use a hybrid or offer &ldquo;Thin&rdquo; events by default for security, with optional expansion for trusted internal consumers <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHJ0JCD1woL-NaTbyw-j7PlMbtwFt8RV_ZHv7G1aloJ4-ZUHNSRId0zvqSgnaI6Ec0QIhOiRuSz_YqX7cTbeOVZpSKllcWSrmdgnqpvVGtlPdGCVZ2uN5Of">39</a>. Ideally, keep payloads under 20kb to reduce transmission overhead <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEzWmUtLUwZardpkMkEkiRaaZ9kjxC2qM52ESoHE9K-Km6HdJrMGp3qvZPFszYFVZI8nX6mL7rraaFWc-eTJ4C6h27FI1ICYJrHqj9T2_oirMHa1SIi8q9eNdqFro_ZwDtGP-se6PNnK6CGFVgD7HwMR9tmQdo2R43ldM65VhuH8syF6-HpRJwW_QofJGiRS3I=">42</a>.</li>
</ul>
<h4 id="43-schema-validation-and-versioning"><strong>4.3 Schema Validation and Versioning</strong></h4>
<p>Webhook payloads constitute an API contract. Breaking changes (removing fields, changing types) can cause downstream failures.</p>
<p><strong>Versioning:</strong></p>
<ul>
<li><strong>Event-Type Versioning:</strong> Versioning is best applied per event type (e.g., v2.invoice.paid vs invoice.paid) rather than a global API version, as this allows granular evolution <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEjc8rQy3dziZ30c5XTfB6f2g7Ro01RtQ4DneCpqeTVp2RbNtd1GqtlzJSZrkJv-BCVl8ANtZAADBSG8yTKyYdHKwQ619at7JzBhX0EDjsUpIyLdfpGQbsNsJgwPYxVqmwrT-A=">43</a>.</li>
<li><strong>Additive Changes:</strong> Schema evolution should be additive (adding new fields is safe; removing fields is breaking). Deprecation periods are required for removing fields <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH55diJUzcJR_u2AWR3Mr6S8HabBX1seyCwwOzAXTTU96fhqNP4RUhs06bklN1nNYw8eTQQd6K9c9NXUVwJ6kJN4y1CD8PMECf1LHUmz7X04825mo3sNLM72Er8DgB0tZ7SSlZl1-y2knwdSj8lxRwVWilPCBC9sGj3LHy_KBcphb5G6bcrehvsRDvgmioQzQepSjQOWAFn">21</a>.</li>
</ul>
<p><strong>Validation:</strong> Consumers should validate incoming payloads against a JSON Schema to fail fast if the data structure is malformed <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE1TYnifHubdovywYD06VK3FtXJhHcToZGBll41Wz8DPfV4_uDdBMVhtWVxvSkoQ69sS-jXup5s_S9ECebOiDmluUQNqH4zKG7jh_HPBKoLMGVEzOH8nF27HUYZ-NReVd9wEPydG49wiPV1A7hvoBk=">44</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEnqdOCSSodq7lxr2auzJYrSMay7bX5jsonu_qtxORBvk9WCP3zotT4Aqkn5aCcuRu27MCmNGYYV62TFJYCh6DTzFyv7RsloSSo84R4Tl31M-BFtBCMdDPUquMu7t4LuQ==">45</a>. However, validation logic should be permissive (&ldquo;tolerant reader&rdquo; pattern)—ignoring unknown fields to maintain forward compatibility <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH55diJUzcJR_u2AWR3Mr6S8HabBX1seyCwwOzAXTTU96fhqNP4RUhs06bklN1nNYw8eTQQd6K9c9NXUVwJ6kJN4y1CD8PMECf1LHUmz7X04825mo3sNLM72Er8DgB0tZ7SSlZl1-y2knwdSj8lxRwVWilPCBC9sGj3LHy_KBcphb5G6bcrehvsRDvgmioQzQepSjQOWAFn">21</a>.</p>
<p><strong>Data Minimization:</strong> To comply with privacy regulations (GDPR, CCPA), payloads should minimize Personal Identifiable Information (PII). Sensitive data should preferably be retrieved via a secure API call (Thin Payload) rather than broadcasted in the webhook <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">6</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mQdSQzonHQsRQLG1x2iQbAc14h1b0QyfDZPE1MsFLTlnRODkc6cJqhFJfn5sTtonCXscipnJQhEOtW1TgQoZt9bJ5wG-o8XJY9hWEw2iHJOXWDuFMxbviQLx5h-_dgoXF-Op">14</a>, <a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFD9i0zWR3HkaI2O4KoryMXqqgfE4NbOWvFDZM_hz0JjFAJXvi8wbVUDXbusd11PlzqseeNcBpFrj-L827YVMqnifjpnPyWo0lYQ71KbDn-gv-hfbN97e7oQTgg-2QikZzqNvujeXzv-3UsFfqNdeKKKSiM6qSsDQ==">41</a>.</p>
<h3 id="conclusion"><strong>Conclusion</strong></h3>
<p>Implementing a robust webhook system requires a holistic approach that balances security, reliability, and efficiency. By securing the transport with HMAC and mTLS, ensuring reliability through idempotency and retries, and designing for scale with asynchronous queues and circuit breakers, developers can build event-driven architectures that are resilient to the chaotic nature of distributed networks. The separation of concerns—where the ingestion layer strictly handles intake and the worker layer handles processing—remains the fundamental architectural pattern for successful high-volume webhook implementations.</p>
<p><strong>Sources:</strong></p>
<ol>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQCXOGuJl7Dh-VhImsadFbEWJxadTB33mDMPL2zHVmbHLv1IfGzFfSEOPmNZolZ00UFxk13zMvSO9B2llkj2RvC8tHuUF3bSc8c9KRD4Ww0yIH_1EbuKPDj03sOz5Y0VsLzTJPPh1TVzK4n5l8kLgSZmrmFROXpNMvIxvs5rjDIr2lIYRLaEUpgcM6uqk=">ngrok.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGapOwdbfMw2xiKf7LQTypi27arEInXlcwMSM0OvS9Irikt1PHqfjKo7eYX1-GSXVUnL1OZKvJPBglA0qm3gwyUNEteJbfdTzPs3rGU1u9KFaVgnecU31VCHI9sAoLW6216rF5E_DmHOhb7g2DP-SfhA7Nv6OxoS08HZEaK-XGw3LBF5NG7W8n4kww_Iw8j6DXuhqLF3g==">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHtLMqFzEPWWTqr57hgCAajV8f2Ij_c5UJeD5WwDVNpUV3KinmSkEGvoP7IlL0h77x5SOjddKsCURFSbuoHB9N0fMQEuZwA6vaxQ3RQb8C4dnCsp7Y0pcosupydpCz5fIJL0A-J8BadBDgNFkVG_TUiGKVx6nm4T8D5sPbuTg==">loginradius.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFS-EcH1LqHZM6UuNXeP-qitgt8WWv5rnN9tweCAL1n2Ek29RI_lGbpCGS6GSkPw2HsuOwmoxLEfSQB5p03WIGFpYueJRELK05i8aXPElmhgw9Wnxt9geuz9jBrOzWvRNiUC2Jtfh0vM0lO2-uzvqU=">stytch.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEXs9GPnNDFPvttHxuwH1fmRNaG7g0AEVP78LNYuD72y5prRsjxAWF1SJ7WgvtvtIU8dmVKOPBXtRWsieTrZQMM7OXct27gnZKRZAdfgsbQTPepbGNrXnoGvjH2hSqBMbdh04kEeOZmgZ3qyw==">dev.to</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH12GnFNNVZoTt8EZ7ZELuQ2jyM2MTTg6fsUKd1Qv0j0xbJyJXE6P5wB89sRaqCHdCdkRPtra8E2C_-lrPXWn96F_s1PpwosEeiwqalYPXcS57HDounBKc5L1EXkPBEtg==">webflow.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE3k1o-3NRkMcffQX8rtbkeOZtI6fj-vyPodgDETt437-jH_lOLx0vKahj92Vaxo28UE8xXjEAAigXWq6jNmbJysY_GhRMoQY9vw_gREej2hXsZ4atFoO-NPyd9pmC9cQkh3VEvU8MCvS8=">webhooks.fyi</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXhbKv9jWGpppHz-I9OAOAkGHbvPQGssu-IzoCcTn7-AJvw_KRC0mtAWzp0ML2hS1UkoasxuhHfOCySlYdoFKmGGbOqw2usCXYiQfNz2wzBJ-46HpNCfh_OiX64LsO2iBrv-nH9tDfEZhTYUlPnjQwmolyARu3jSigFIC8KYykE04XjV8ccUWP_CLCz6tVC516k0WI2SfUEoFzX1uQhE6xJa4rDEq6JhURZuEt7rhKDNqURA==">latenode.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEDIwllRPtSE_Pf62YeXgIYgsDEfs-1wHuv9ezOKwFYSaEc39N4e_yAOhbtRX1t-lLnt_VqbdnEsbq1uSII6uRxqit-ZihDjM4StrhiNYPplF_yDLSKzglCmQg_JtAsbsmocQQU6WsYQo0kdLodcetTYvUPbQWjEMuZcovNsV0IvMRm4JFcWykx9BIBvrIMKg999w==">security.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGImss29WCJrZ-7A1qRvtpSXx2770lcn-FKzrohjNSvahorZqWNKElMcdM8ncv0_woeJHwVb1-xgukNmufkutg13vEv6wXuSWlpA5Xez7xFPYWUO9B1_c1B645Bj1Tfjmr8KEQr8A==">webhookrelay.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFYYHzohwba_5v9CJiFs8mD1iz9fbhE8MtwfSvmrUpiSIuWRvl2kvOSLUz9Uvjk6_5n-fLTAnIyoWJe2zh6vCb1i0m2bmHtT2vlK_jU2o-aPA0hlfcOtinI7exwqNXqj9Vrv1Oq6--c0TPo-NGY23eKvhemf-YeZ6UQWTITLFB1oWHphtQzzR2dfuIB4IYT3URzUbF8VVUosF8=">techradar.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHyAw6S_aOVlsVoLFQg0ZMCQlWmrBgVHp4dBnn2xttREglexTIahmODOAstaUnlwsNIP87xlAcCBSasuOixIkwZ6P5b6tzO9rqx4rahyPJ98wCyFn9P_A3mpjCOu7wNm_xYBANICeyhNA==">dev.to</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH1xca3J3HdA9cCG1LqCARxi2f2drvrB3kvAEkgwonEY73ciX1Lfc37H71xRSwdAwybTc7UIK6pLHVBmlgaa5oQG-hkozuAkTN_ESLLLgMFL3uXCe_YKsU4QDCfxWE9V8YUG8_9iVClMHZsmwa0Ny7ZUUTFq_7IPBGSW85ourPVJ9I=">hookdeck.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mQdSQzonHQsRQLG1x2iQbAc14h1b0QyfDZPE1MsFLTlnRODkc6cJqhFJfn5sTtonCXscipnJQhEOtW1TgQoZt9bJ5wG-o8XJY9hWEw2iHJOXWDuFMxbviQLx5h-_dgoXF-Op">snyk.io</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQfVAEpbMLruPbjNGZ738eh_ylZHWHPnu_qZm9XTLHB-AT0BmDjUV_H1Lv00QMDQhFo9DgyxKxPUtoHpGCr0y7XlujSMPRVNqbFaRcmSCOGLV78IAM-ewJFRv_ttzj2JOp_tgD2udxcyAIsAUbcLhsS1uYoNKoJkI=">hookdeck.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEHXKe8AnCoBNUic4_dIw3dZ5D6GIcUTosndmVu1nzaiSfbBhtV04q7oMzc4l71nWdcRfuExdy1qe2cHZj_2E8QGFjwSWWlQthKKPP3YDgs9Wm5ZiXPAS6z7rAhkvllqe6Uw73rz9UGA1IPPZ_-RI4h124D0AocAIjiY798wYZn5_k0U_K1qAcdSG9jxHTwuFcaEEUz9g==">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEATF-8ojdZKpkXPPqDJmcexidA8Fo4g8M1737OwRPdyZbSoXi0RKkhctGTp62J5PReRU1dihQLC-Rr9o32-K-Gy2KjqBWlMjZa1hW2qWQtCVPYiNMXB-p1jRIfT9xLvPQL84kZg3YwHlxDck22cDUqRP94Uc7d3W5HuZNc2A87PCmU8peDNqbza0zPKZmTQuFZ9gaJsw==">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHMukyFGeJn3lzizl34s01hqUqLH39KV4C3rqM6KdUEdBXPX9w1RSuEFAKZT_UX0C1NZu8KL_-4t3UzO0_cg69Nb_JI_u4dk8GMpFAhLqOOqPO8FuD3hgDsb45rLo0v8o1ZlOeLBpF2ZcZ3KXE7CPR-5ErLgSbfb9k8mPvJXOnpxUg1xtJoa0ynC8XFF0c=">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHdp9hSU08oYMf-4GdNFGBYU4v4d93LvZ1d_ICLNnJcPAG2fUTOKHohQ-kOnJsaA6Teu2FI8Ut30grgFQBpXeaydSjx9bUWXZoU_hSLg_aL73uSS9CsVmK0LFDKAtq3YG4D31h1Kuszk39-Lk3nu09_NajWdBOTQy-_M8QxfRrEU9fL6K4WYNU28bRd_69TmlowBm2krMq_IWLW-qzdtaPgV3zuPDnB1w==">hookdeck.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH0JVPUOmptuBYlB8-wJwyjeuTAVRVyUb223KovQt8MsM-mlOtej6D_kcchgaC2ZwcjcEx0wq13bm76jEk23wIs0we5HpQ0QAgfz9A2FMmhmzNDMGbHA_jg0dMemVpSjrN_B-dQh6lKCPSh52kD7ND2Z6JdPrzXn_zdyqD3Tv4NZzOIHCfNK5myi6e-LOOUybc=">dev.to</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH55diJUzcJR_u2AWR3Mr6S8HabBX1seyCwwOzAXTTU96fhqNP4RUhs06bklN1nNYw8eTQQd6K9c9NXUVwJ6kJN4y1CD8PMECf1LHUmz7X04825mo3sNLM72Er8DgB0tZ7SSlZl1-y2knwdSj8lxRwVWilPCBC9sGj3LHy_KBcphb5G6bcrehvsRDvgmioQzQepSjQOWAFn">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQED662YLvMnAViKnlF958nxB2BmJpS8SQlmVbMuE3Ko2pNQr0b3kaD7B_hyk1HUebgY3IcCs7tGCNRnInAdhxH-LtRvP6C-8byHujzYJChaayy4NZ_BaC3c4VkXU2dSYKuJ">youtube.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFd_CxUJPKeVMz1F02SqlVQjKBwFSscQ8mtFX755DcIRg6EU2j5-mjqOJu8hflle4-VnsqJbFMLbfTmn5fZUWuoq6RBBxRAlRKJWTYK0i9j9b7_ZTFlbVCRcc2DLxpvESUfrCiUPBj_a01pRXCaQ3lSo4v-5yuecvnWx6GAnnWg8M4vN4G1PnrSbZL-XcgBmsFqINYgUuI0b3yQR_BgMITXm7G1BCZ2Mm4=">dev.to</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHJ3oedQbVxZag1bGXe54yIdze9Q9z8q0IPuWb3dPI47uWDHpqJz9cHmjP5-hZ6XYoQ8R-jYwG0hwIahcK4RbJwtYnyZElCsPNpqm4y1nzN-ClzqQ8DV2Fn9v69cFHr-NZnLfaKp7gy1fnIyHCorG7A-TaoWdCCR-6B5Y4CS0vP--U69OqbFvNGJnQB8klIbl36rFWVvvGG6A==">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkjQjNpTsb8JZ5qe59IFdmmAjGw8COOq_1pGnrUe37t1NDdlwblViRj7cSp7gccbNFGm1n5EQ1r6szF-x9YqTl4P5yKbnDbwfON9RSKjv3IwjZeKTSgVWmATqBkax3VdITfVgQKQpQD8GePx0=">hookdeck.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF99_r_zUtU_jYwFqB_uQJoJXExrO3kKFls8vi9mILvSgAUmdwy6FH_pE_OR796JSUEUJ-tYDqUfYDkStQBVIv7gcPbezN4RfgTSTIUFP-rXh24CgzRm3yr4JFMeqO4WX9_i2cUz6_765Rj7HRrVEEO7tWyRA==">svix.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGrBXavT7Taw429hZQnfpdVUgMSqV0qPk3J1Kr1FYj9RI1REkA6gaHCgEcJpIE3pUUB5LOxm1mWgiZMr7bT1JsOLoNm7PO4TYhMMyLwJ0_HD95w9WrxiqJoLJVJJ4weqq7Xfaa1dOHz1gjxeHd8eNPeQRXEHsJFi6DwWKjldZa99ugI7ObRlXaXiAtlVPdAiY2I8L8pual1oHwnmbgajvVoMq1hSwplMf0ue9Y=">latenode.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEW2f40SZCjfELqmNW1j9NyGNsCNsip2VIO51aLp1fUFClxVo6-KLqVo1OfM-Bevq9PGU_Kf_38maQ4Jmrdxqg7x_H5FmQ3dlUczpv5puiR6LdHG7CPYhZ14eWfHt7G8LHnwfjk_CCMF64KmU9bR5uoSNqShdc6ddPcjZW8_eOBw7M=">amazon.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHx0DNAVU9VSUpsBSRtjwACgynyVes1j87NAFlezz2pJ6BkCNAnlKrz1tHo0I793oZ8cfTFVct_0ueqFEz3OtLjiWcLCX90oM1Ux_BZuWr7l6PzvEaImxxrTqIy9M5NMn_RDEG3Gk-wfJHvdOF8XC-u1A==">integrate.io</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEii1RdCo-T6eB4tvb4eqOGUFXXz86BAwvINlXVzeqixPAe4JoWDgnCEBSXVxkmYsG2grSzKTsx0HES6tGxKTNLiu4QWsF8F_KirhZWy4T34iyby3QYriXlFhsUBmvP33AQ5xkmqsPGex3VIzZKEhLDJDWx491NGX7SysFplvIamdaIGyhvkTcQnifz1eRmdo5qs1BpvdiRMmCin3gY6-IZcf3KJA==">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF4clgaz1Awh_GB47zB8l4w-lRCthsTIWD6SuBJwCTCHaGi9JO0Ap-1rMelQreT6KjtzTWqtapMz6xhm_H3pX7tKtALVd6i8YVM8lV0vuXQuO7ttX5E3ySHcZQPI0sncecV5S_rsxYmhMjxKUkkqxraEdedxVDbgUZvvWCUj0Lcvg==">mambu.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF3SixGEwLnJSSDKs0a8OOppywsIW7q2J8aZjaO7L1PLb6bH5CAnbroqLQwsBO0dZZsGDOtgF4aLdZLQeJX-fIw-RqvbES0LRDmTCZyGqvhBk-TVkS3P-XjxaDWdiptgeVjhOTOkUN_0lgqzkZk_G8ObsMcZlqoMpdvykuzrSuRkMSFwJrzsv-Za74qFDK3Y4Ko">stackoverflow.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHnX_dTTnINzT7X8e5hlMotKAfsSndIjiQk6jQzsN796QPbtzgqxv0KSq6614IFuC9MAo0fT6uUi2CDRsw6G53zjPoVha2es6TiRmz4pIDeoSqk0fWjovhwbbG5xLVvm5-w1FC_vXwRiop2PfXAg089Czp7PKLpL6MyoDQT5Js3vAPE0VXfSxsBgTC6ytO63A==">raymondtukpe.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGlXCpA03cjm2HhSB_1tZBCddlHw7t9zrzGIWVyeXRPhmyshdf1u7gelYqt4L3TVML4ucYYal6EABFM4XOGo8fz22QJzpFqFiSJFwsJsCTx1dCeqo0uBGSUyYFCE8JHAwxUNXmLt6cLY6hv0FYzRnFRFHFpj2_4l1Fv9HLO8X0Mzlc0XAdVF_GB5r9ViToyKWk=">hookdeck.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEZ-84ZM44XCpM1vFVUNfrYG0zPZ6SeH4Y4G_nIIuv2XBee_SscnA4KYHIYW0g3CGFzDadJjeR0BpGnOwWpreUXbvbMWdcWDvObDBqUN-mKwKQMV8DHKCYKtBhcz4Ex5nSeg10PQzabmIIMqEfNMxhFt-CAL-1yOA==">trackunit.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGi86SD96fW-xFK_X84I9lPylpM6VBZGAFibU34NUtVmx1LcRW_HLP4fOYtJv2_vNff25pqSYwAq09S2tH4W6kCBXrao2NytEeUU0voKlbGnJfgslr823r8QuWJvJj3FPv3qh8i9Xwwx1VcRZbWiwCnjYdbQj-tVOgPkWIvrew8cQShYszht98=">medium.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHKAYtR111JaUonVfus71SW3CQhTGuLaDlWY4AUaUzUzmLsqVmLSHvk32ut_5dkYafGZDCGL7ci6IFm4zM4TB160dmZLb7vILSvAOb99uHcarUrBEtL_ixdCugN60yQge0LND250y0z0JZXJMA=">codesimple.blog</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEurOYKLl9vW8_iQij2gRKetKr5a55aofIDB6vAl2O4TRlpJGiu4sBVeyq1l2RglZ0GgTjjoEp9Ig_MPyP5kdg7zbBA2jKLcn4-n-fczZkQw5joT-5kkDiJZfXjZz2KENyZ4XiF2PmJ-Cr99LHk">codeopinion.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHJ0JCD1woL-NaTbyw-j7PlMbtwFt8RV_ZHv7G1aloJ4-ZUHNSRId0zvqSgnaI6Ec0QIhOiRuSz_YqX7cTbeOVZpSKllcWSrmdgnqpvVGtlPdGCVZ2uN5Of">brianlovin.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH7IgdgnjAYn56-UBWDUh9NHS55EnqLdOmTTx2JtV41oh7Wb7TjWnjHmNS-qkW3fAjQyvGFwbmCFyfAon-Lp7X1xJxaZAy9eHfFlEAXO9K3AJQ0U02vMI-YRUUpMK7Eqa5Q">hookdeck.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFD9i0zWR3HkaI2O4KoryMXqqgfE4NbOWvFDZM_hz0JjFAJXvi8wbVUDXbusd11PlzqseeNcBpFrj-L827YVMqnifjpnPyWo0lYQ71KbDn-gv-hfbN97e7oQTgg-2QikZzqNvujeXzv-3UsFfqNdeKKKSiM6qSsDQ==">mendix.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEzWmUtLUwZardpkMkEkiRaaZ9kjxC2qM52ESoHE9K-Km6HdJrMGp3qvZPFszYFVZI8nX6mL7rraaFWc-eTJ4C6h27FI1ICYJrHqj9T2_oirMHa1SIi8q9eNdqFro_ZwDtGP-se6PNnK6CGFVgD7HwMR9tmQdo2R43ldM65VhuH8syF6-HpRJwW_QofJGiRS3I=">github.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEjc8rQy3dziZ30c5XTfB6f2g7Ro01RtQ4DneCpqeTVp2RbNtd1GqtlzJSZrkJv-BCVl8ANtZAADBSG8yTKyYdHKwQ619at7JzBhX0EDjsUpIyLdfpGQbsNsJgwPYxVqmwrT-A=">svix.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE1TYnifHubdovywYD06VK3FtXJhHcToZGBll41Wz8DPfV4_uDdBMVhtWVxvSkoQ69sS-jXup5s_S9ECebOiDmluUQNqH4zKG7jh_HPBKoLMGVEzOH8nF27HUYZ-NReVd9wEPydG49wiPV1A7hvoBk=">inventivehq.com</a></li>
<li><a href="https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEnqdOCSSodq7lxr2auzJYrSMay7bX5jsonu_qtxORBvk9WCP3zotT4Aqkn5aCcuRu27MCmNGYYV62TFJYCh6DTzFyv7RsloSSo84R4Tl31M-BFtBCMdDPUquMu7t4LuQ==">zuplo.com</a></li>
</ol>
</details>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>How to Integrate Gemini CLI with Intellij Idea Using ACP</title><link>https://glaforge.dev/posts/2026/02/01/how-to-integrate-gemini-cli-with-intellij-idea-using-acp/</link><pubDate>Sun, 01 Feb 2026 18:50:01 +0100</pubDate><guid>https://glaforge.dev/posts/2026/02/01/how-to-integrate-gemini-cli-with-intellij-idea-using-acp/</guid><description>&lt;p>The &lt;strong>Agent Client Protocol (&lt;a href="https://agentclientprotocol.com/get-started/introduction">ACP&lt;/a>)&lt;/strong>
allows you to connect external AI agents directly into IDEs and text editors that support that protocol (like JetBrains&amp;rsquo; IntelliJ IDEA, PyCharm, or WebStorm, as well as &lt;a href="https://zed.dev/">Zed&lt;/a>).
This means you can bring the power of the &lt;strong>Gemini CLI&lt;/strong> directly into your editor, allowing it to interact with your code, run terminal commands,
and use Model Context Protocol (MCP) servers right from the AI Assistant chat window.&lt;/p></description><content:encoded>
<![CDATA[<p>The <strong>Agent Client Protocol (<a href="https://agentclientprotocol.com/get-started/introduction">ACP</a>)</strong>
allows you to connect external AI agents directly into IDEs and text editors that support that protocol (like JetBrains&rsquo; IntelliJ IDEA, PyCharm, or WebStorm, as well as <a href="https://zed.dev/">Zed</a>).
This means you can bring the power of the <strong>Gemini CLI</strong> directly into your editor, allowing it to interact with your code, run terminal commands,
and use Model Context Protocol (MCP) servers right from the AI Assistant chat window.</p>
<p><strong>This guide will walk you through setting up <a href="https://geminicli.com/">Gemini CLI</a> as a custom agent in <a href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a>.</strong></p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>The JetBrains AI Assistant <a href="https://www.jetbrains.com/help/ai-assistant/acp.html#install-agent-from-registry">help pages</a> mention
that it should be possible to install an AI agent from the ACP registry, but this option wasn&rsquo;t available for me, at the time of this writing.
Hence why I decided to investigate and write this tutorial!</p>
      </div>
    </div><h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><strong>IntelliJ IDEA</strong> (or other JetBrains IDEs) version <strong>2025.3</strong> or later.</li>
<li><strong>Node.js</strong> installed (version 20+ recommended), needed by Gemini CLI.</li>
<li><strong>Gemini CLI</strong> installed.</li>
</ul>
<h2 id="step-1-install-gemini-cli">Step 1: Install Gemini CLI</h2>
<p>If you haven&rsquo;t already, install the Gemini CLI globally using npm:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install -g @google/gemini-cli
</span></span></code></pre></div><p>Once installed, verify it works by running:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gemini --version
</span></span></code></pre></div><h2 id="step-2-locate-the-gemini-executable">Step 2: Locate the Gemini Executable</h2>
<p>You need the absolute path to the installed <code>gemini</code> executable for the configuration file.</p>
<p><strong>On macOS / Linux:</strong>
Run the following command in your terminal:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>which gemini
</span></span></code></pre></div><p><em>Example output:</em> <code>/Users/username/.nvm/versions/node/v22.16.0/bin/gemini</code></p>
<p><strong>On Windows:</strong>
Run the following command in Command Prompt or PowerShell:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#007020">where </span>gemini
</span></span></code></pre></div><p><em>Example output:</em> <code>C:\ Program Files\nodejs\gemini.cmd</code> (or similar inside <code>AppData</code>)</p>

    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>Copy this path; you will need it for the next step.</p>
      </div>
    </div><h2 id="step-3-configure-the-acp-agent">Step 3: Configure the ACP Agent</h2>
<p>JetBrains IDEs look for a specific JSON configuration file to load external agents.
You need to create or edit this file.</p>
<p><strong>File Location:</strong></p>
<ul>
<li><strong>macOS / Linux:</strong> <code>~/.jetbrains/acp.json</code></li>
<li><strong>Windows:</strong> <code>%USERPROFILE%\.jetbrains\acp.json</code></li>
</ul>
<p><strong>Configuration Content:</strong></p>
<p>Create the file (if it doesn&rsquo;t exist) and add the following JSON content.
Paste the path you found in Step 2 into the <code>&quot;command&quot;</code> field.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;agent_servers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;Gemini CLI&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;command&#34;</span>: <span style="color:#4070a0">&#34;/path/to/your/gemini&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;--experimental-acp&#34;</span>
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;use_idea_mcp&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;use_custom_mcp&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>
    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>Be sure to set the <code>--experimental-acp</code> flag, as it&rsquo;s still an experimental feature in Gemini CLI.</p>
      </div>
    </div><h3 id="configuration-breakdown">Configuration Breakdown:</h3>
<ul>
<li><strong><code>&quot;Gemini CLI&quot;</code></strong>: This is the display name you will see in the IDE.</li>
<li><strong><code>&quot;command&quot;</code></strong>: The absolute path to the Gemini executable.</li>
<li><strong><code>&quot;args&quot;</code></strong>: We pass <code>--experimental-acp</code> to tell Gemini to start in Agent Communication Protocol mode. You can also pass other <a href="https://geminicli.com/docs/cli/cli-reference/#cli-options">flags</a> supported by Gemini, for example for forcing a particular Gemini model version, etc.</li>
<li><strong><code>&quot;use_idea_mcp&quot;: true</code></strong>: This is crucial. It allows Gemini to access the IDE&rsquo;s built-in Model Context Protocol (MCP) server, giving it context about your open files, project structure, and more.</li>
<li><strong><code>&quot;use_custom_mcp&quot;: true</code></strong>: Allows Gemini to use any other custom MCP servers you might have configured in the IDE.</li>
</ul>
<h2 id="step-4-restart-and-connect">Step 4: Restart and Connect</h2>
<ol>
<li><strong>Restart</strong> your IntelliJ IDEA to load the new configuration.</li>
<li>Open the <strong>AI Assistant</strong> tool window (usually on the right side).</li>
<li>Look for the <strong>Agent Selector</strong>. It might be a dropdown menu at the top of the chat or a &ldquo;More&rdquo; (&hellip;) menu.</li>
<li>Select <strong>&ldquo;Gemini CLI&rdquo;</strong> from the list.</li>
</ol>
<p>In the AI Chat window, you should see something like this:</p>
<p><figure>
  <a href="#img-7f82f3fb73f1306dd3bf7abe27b62415">
    <img src="/img/gemini-cli/acp-intellij-gemini-cli.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-7f82f3fb73f1306dd3bf7abe27b62415">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/acp-intellij-gemini-cli.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Select <code>Gemini CLI</code> from that drop-down menu.
And you&rsquo;re good to go!</p>
<h2 id="usage">Usage</h2>
<p>Once selected, you can chat with Gemini just like the default AI assistant, but with the added capabilities of the CLI!</p>
<ul>
<li><strong>Context Awareness:</strong> It knows about your project files via the IDE&rsquo;s MCP.</li>
<li><strong>Tool Use:</strong> It can perform actions defined in the CLI&rsquo;s toolset.</li>
</ul>
<h2 id="troubleshooting">Troubleshooting</h2>
<ul>
<li><strong>Agent not appearing?</strong> Double-check the path in <code>acp.json</code>. If you use <code>nvm</code> (Node Version Manager), ensure the path points to the specific version currently in use, not a generic alias that might not be available to the IDE&rsquo;s environment.</li>
<li><strong>Permissions:</strong> On macOS/Linux, ensure the file <code>~/.jetbrains/acp.json</code> is readable by your user.</li>
<li><strong>Experimental Flag:</strong> Ensure you didn&rsquo;t forget the <code>--experimental-acp</code> argument; otherwise, the CLI will try to launch in interactive terminal mode and hang.</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Javelit Frontend for the Deep Research Agent</title><link>https://glaforge.dev/posts/2026/01/30/a-javelit-frontend-for-the-deep-research-agent/</link><pubDate>Fri, 30 Jan 2026 14:00:54 +0100</pubDate><guid>https://glaforge.dev/posts/2026/01/30/a-javelit-frontend-for-the-deep-research-agent/</guid><description>&lt;p>A month ago, I wrote about &lt;a href="https://glaforge.dev/posts/2026/01/03/building-a-research-assistant-with-the-interactions-api-in-java/">building a research assistant&lt;/a> in Java,
using the Gemini Interactions API, and the Deep Research agent.
Today I wanted to revisit this project, but with the goal to make it more user-friendly, with a &lt;a href="https://javelit.io/">Javelit&lt;/a> based frontend.&lt;/p>
&lt;h2 id="the-research-pipeline-from-query-to-infographic">The Research Pipeline: From Query to Infographic&lt;/h2>
&lt;ul>
&lt;li>First, the user enters the subject of the research.&lt;/li>
&lt;li>A button action triggers the &lt;strong>research of possible topics about that subject&lt;/strong> (ie. the different possible facets or angles of the subject), using &lt;strong>Gemini 3 Flash with Google Search&lt;/strong> activated.&lt;/li>
&lt;li>The user selects the facets they&amp;rsquo;re interested in, to restrict the research to only those aspects.&lt;/li>
&lt;li>Then the &lt;strong>Deep Research agent&lt;/strong> kicks in, via the Gemini Interactions API, and spends a few minutes researching the topic.&lt;/li>
&lt;li>Once the final report is ready, &lt;strong>Gemini 3 Pro creates a solid summary&lt;/strong>.&lt;/li>
&lt;li>The summary is used to generate a &lt;strong>sketchnote with Nano Banana Pro&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h2 id="a-look-at-the-user-interface">A Look at the User Interface&lt;/h2>
&lt;p>Let&amp;rsquo;s say, as a user, I want to research information about the &lt;em>OpenClaw / MoltBot / ClawdBot personal AI assistant&lt;/em> (unless it has again changed its name? &amp;#x1f603;)&lt;/p></description><content:encoded>
<![CDATA[<p>A month ago, I wrote about <a href="https://glaforge.dev/posts/2026/01/03/building-a-research-assistant-with-the-interactions-api-in-java/">building a research assistant</a> in Java,
using the Gemini Interactions API, and the Deep Research agent.
Today I wanted to revisit this project, but with the goal to make it more user-friendly, with a <a href="https://javelit.io/">Javelit</a> based frontend.</p>
<h2 id="the-research-pipeline-from-query-to-infographic">The Research Pipeline: From Query to Infographic</h2>
<ul>
<li>First, the user enters the subject of the research.</li>
<li>A button action triggers the <strong>research of possible topics about that subject</strong> (ie. the different possible facets or angles of the subject), using <strong>Gemini 3 Flash with Google Search</strong> activated.</li>
<li>The user selects the facets they&rsquo;re interested in, to restrict the research to only those aspects.</li>
<li>Then the <strong>Deep Research agent</strong> kicks in, via the Gemini Interactions API, and spends a few minutes researching the topic.</li>
<li>Once the final report is ready, <strong>Gemini 3 Pro creates a solid summary</strong>.</li>
<li>The summary is used to generate a <strong>sketchnote with Nano Banana Pro</strong>.</li>
</ul>
<h2 id="a-look-at-the-user-interface">A Look at the User Interface</h2>
<p>Let&rsquo;s say, as a user, I want to research information about the <em>OpenClaw / MoltBot / ClawdBot personal AI assistant</em> (unless it has again changed its name? &#x1f603;)</p>
<p><figure>
  <a href="#img-b6abd74633a93990d3932c54635418c3">
    <img src="/img/gemini/interactions/dr-jt/agent-1.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b6abd74633a93990d3932c54635418c3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-1.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>With Javelit, the code looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Java" data-lang="Java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// The main title</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;🔎 Deep Research Agent&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// A header</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">header</span>(<span style="color:#4070a0">&#34;Subject&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The form containing the text area and submit buttons</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>formSubject<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">form</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;form_subject&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>subject<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textArea</span>(<span style="color:#4070a0">&#34;Subject&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;subject&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">placeholder</span>(<span style="color:#4070a0">&#34;Enter the subject you want to research...&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(formSubject);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The buttons (submit and clear) are inside 2 columns on a row</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>columns<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">columns</span>(2).<span style="color:#4070a0">widths</span>(List.<span style="color:#4070a0">of</span>(0.<span style="color:#4070a0">9</span>,<span style="color:#bbb"> </span>0.<span style="color:#4070a0">1</span>)).<span style="color:#4070a0">use</span>(formSubject);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// I clear the state if the user clicks the clear button</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">formSubmitButton</span>(<span style="color:#4070a0">&#34;Clear All&#34;</span>).<span style="color:#4070a0">onClick</span>(b<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">setComponentState</span>(<span style="color:#4070a0">&#34;subject&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">remove</span>(<span style="color:#4070a0">&#34;topics&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}).<span style="color:#4070a0">use</span>(columns.<span style="color:#4070a0">col</span>(1));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// We proceed with the next steps</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// if the user clicks the exploration button</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">formSubmitButton</span>(<span style="color:#4070a0">&#34;Explore Topics&#34;</span>).<span style="color:#4070a0">type</span>(<span style="color:#4070a0">&#34;primary&#34;</span>).<span style="color:#4070a0">onClick</span>(b<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">remove</span>(<span style="color:#4070a0">&#34;topics&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}).<span style="color:#4070a0">use</span>(columns.<span style="color:#4070a0">col</span>(0));<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="1-topic-exploration-gemini-3-flash">1. Topic Exploration (Gemini 3 Flash)</h3>
<p>Then, I click on <code>Explore Topics</code> to find the various facets of that story.
Looks like Gemini 3 Flash is thinking, and is actively searching for the most up-to-date information on Google Search:</p>
<p><figure>
  <a href="#img-3e16b09610e5fbf1d4ac74d744cd9440">
    <img src="/img/gemini/interactions/dr-jt/agent-2.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3e16b09610e5fbf1d4ac74d744cd9440">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-2.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Indeed, I created a <em>model</em> interaction invoking Gemini 3 directly, and requesting to return a <strong>structured output</strong> (an array of strings),
containing the different facets of the subject:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>planParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-flash-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                Find a list of topics to research
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                on the following subject:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                &#34;&#34;&#34;</span>,<span style="color:#bbb"> </span>state.<span style="color:#4070a0">subject</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseFormat</span>(GSchema.<span style="color:#4070a0">fromClass</span>(String<span style="color:#666">[]</span>.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearch())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">store</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>planInteraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(planParams);<span style="color:#bbb">
</span></span></span></code></pre></div><p>In terms of UI, as the UI elements are rendered synchronously, as the code is being executed, we can define placeholder elements that will receive future components:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// A header</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">header</span>(<span style="color:#4070a0">&#34;Topics&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The form containing the text area and submit buttons</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>formTopics<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">form</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;form_topics&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// An &#34;empty&#34; container to hold an info bubble and the future topics</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>topicsContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">empty</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;topics_container&#34;</span>).<span style="color:#4070a0">use</span>(formTopics);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Preparing topics...&#34;</span>).<span style="color:#4070a0">icon</span>(<span style="color:#4070a0">&#34;:hourglass:&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(topicsContainer);<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the above, the <code>Jt.empty()</code> component receives the <code>Jt.info()</code> bubble.
But once the list of facets is returned by the model interaction, the info bubble will be replaced by a list of checkboxes with the topics to select:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>topicSelectionContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">container</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;topics&#34;</span>).<span style="color:#4070a0">use</span>(topicsContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>selectedTopics<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>topics.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">filter</span>(topic<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Jt.<span style="color:#4070a0">checkbox</span>(topic).<span style="color:#4070a0">use</span>(topicSelectionContainer))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">toList</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">formSubmitButton</span>(<span style="color:#4070a0">&#34;Launch Research&#34;</span>).<span style="color:#4070a0">type</span>(<span style="color:#4070a0">&#34;primary&#34;</span>).<span style="color:#4070a0">use</span>(formTopics);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(selectedTopics.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// wait for user to select topics and hit form submit button</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini 3 found a good list of facets. Let me select the ones I&rsquo;m the most interested in:</p>
<p><figure>
  <a href="#img-fe9027fb45139a4e33de1d18a8d9a83d">
    <img src="/img/gemini/interactions/dr-jt/agent-3.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-fe9027fb45139a4e33de1d18a8d9a83d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-3.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h3 id="2-the-deep-dive-deep-research-pro">2. The Deep Dive (Deep Research Pro)</h3>
<p>Now when I click the <code>Launch Research</code> button, the <strong>Deep Research</strong> agent is actively working:</p>
<p><figure>
  <a href="#img-025a1297527f3a20379fee742aabbf47">
    <img src="/img/gemini/interactions/dr-jt/agent-4.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-025a1297527f3a20379fee742aabbf47">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-4.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In this part of the interface, you&rsquo;ll notice the use of tabs, to separate the full report, the summary, and the infographic.
This is achieved with the <code>Jt.tabs()</code> component.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// A header</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">header</span>(<span style="color:#4070a0">&#34;Report&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The 3 tabs</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>tabLabels<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;Full Report&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Summary&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Infographic&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>tabs<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">tabs</span>(tabLabels).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Each tab has a placeholder</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>reportPlaceholder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">empty</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;fullReport&#34;</span>).<span style="color:#4070a0">use</span>(tabs.<span style="color:#4070a0">tab</span>(tabLabels.<span style="color:#4070a0">get</span>(0)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>summaryPlaceholder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">empty</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;summary&#34;</span>).<span style="color:#4070a0">use</span>(tabs.<span style="color:#4070a0">tab</span>(tabLabels.<span style="color:#4070a0">get</span>(1)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infographicPlaceholder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">empty</span>().<span style="color:#4070a0">key</span>(<span style="color:#4070a0">&#34;infographic&#34;</span>).<span style="color:#4070a0">use</span>(tabs.<span style="color:#4070a0">tab</span>(tabLabels.<span style="color:#4070a0">get</span>(2)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The placeholders are info bubbles,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// soon replaced by the report, summary, and infographic</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Preparing full report...&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">icon</span>(<span style="color:#4070a0">&#34;:hourglass:&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(reportPlaceholder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Preparing summary...&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">icon</span>(<span style="color:#4070a0">&#34;:hourglass:&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(summaryPlaceholder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Preparing infographic...&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">icon</span>(<span style="color:#4070a0">&#34;:hourglass:&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(infographicPlaceholder);<span style="color:#bbb">
</span></span></span></code></pre></div><p>As the research and thinking progress, <strong>Deep Research</strong> shares its thoughts and current actions with me
(the info bubbles being replaced by the thoughts and actions):</p>
<p><figure>
  <a href="#img-552574693ec1ccf724e6549e18cfe4e0">
    <img src="/img/gemini/interactions/dr-jt/agent-5.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-552574693ec1ccf724e6549e18cfe4e0">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-5.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>It&rsquo;s important to pause to see how the thoughts are streamed in real time.</p>
<p>Let&rsquo;s have a look at the agent interaction definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>AgentInteractionParams<span style="color:#bbb"> </span>researchParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AgentInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">agent</span>(<span style="color:#4070a0">&#34;deep-research-pro-preview-12-2025&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Write a concise research report on the following subject:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &lt;subject&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &lt;/subject&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            By focusing on the following topics:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &lt;topics&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &lt;/topics&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>subject,<span style="color:#bbb"> </span>topicsList))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">background</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">stream</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">agentConfig</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>DeepResearchAgentConfig(ThinkingSummaries.<span style="color:#4070a0">AUTO</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">store</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>The important bits here are:</p>
<ul>
<li><code>background(true)</code> to state it&rsquo;s a background operation that can take time to complete.</li>
<li><code>stream(true)</code> to state it should be streamed in real time.</li>
<li>And <code>.agentConfig(new DeepResearchAgentConfig(ThinkingSummaries.AUTO))</code> says that thoughts should be sent as they occur.</li>
</ul>
<p>The part taking care of the streaming is the <code>stream()</code> method on the agent interaction:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>client.<span style="color:#4070a0">stream</span>(researchParams).<span style="color:#4070a0">forEach</span>(event<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(event<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>ContentDelta<span style="color:#bbb"> </span>delta)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(delta.<span style="color:#4070a0">delta</span>()<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>ThoughtSummaryDelta<span style="color:#bbb"> </span>thought)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(thought.<span style="color:#4070a0">content</span>()<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>TextContent<span style="color:#bbb"> </span>textContent)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Jt.<span style="color:#4070a0">markdown</span>(textContent.<span style="color:#4070a0">text</span>()).<span style="color:#4070a0">use</span>(reportPlaceholder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(delta.<span style="color:#4070a0">delta</span>()<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>TextDelta<span style="color:#bbb"> </span>textPart)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>reportBuilder.<span style="color:#4070a0">append</span>(textPart.<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Jt.<span style="color:#4070a0">markdown</span>(reportBuilder.<span style="color:#4070a0">toString</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">use</span>(reportPlaceholder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>The client is receiving thoughts, but it&rsquo;s also later going to receive the report in the stream.
So as soon as thoughts or pieces of the report arrive, they are reflected in the UI of our research agent.</p>
<p>And after a little while (from one to six minutes or so) the final report is ready:</p>
<p><figure>
  <a href="#img-0d3157b93b094149e28b7566b3bd79d9">
    <img src="/img/gemini/interactions/dr-jt/agent-6.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0d3157b93b094149e28b7566b3bd79d9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-6.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h3 id="3-synthesis-gemini-3-pro">3. Synthesis (Gemini 3 Pro)</h3>
<p>But I can go straight to the <em>TL;DR</em>, because Gemini 3 Pro will have prepared a high-level summary of the report:</p>
<p><figure>
  <a href="#img-5b7a47074b226a06e5cb2310ff1006dd">
    <img src="/img/gemini/interactions/dr-jt/agent-7.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-5b7a47074b226a06e5cb2310ff1006dd">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-7.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The summary is prepared by a call to Gemini 3 Pro:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>summaryParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-pro-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Create a concise summary of the research below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Go straight with the summary, don&#39;t introduce the summary
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        (don&#39;t write &#34;Here&#39;s a summary...&#34; or equivalent).
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>,<span style="color:#bbb"> </span>reportBuilder))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">store</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="4-visualizing-with-infographics-gemini-3-pro-image">4. Visualizing with Infographics (Gemini 3 Pro Image)</h3>
<p>Thanks to the talent of &#x1f34c; <strong>Nano Banana Pro</strong>:</p>
<p><figure>
  <a href="#img-bab0713c8fca7141ad895440d131b538">
    <img src="/img/gemini/interactions/dr-jt/agent-8.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-bab0713c8fca7141ad895440d131b538">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/dr-jt/agent-8.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>For generating the image, we just pass the summary to the model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infographicParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-pro-image-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Create a hand-drawn and hand-written sketchnote
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        style summary infographic, with a pure white background,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        use fluorescent highlighters for the key points,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        about the following information:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>,<span style="color:#bbb"> </span>summaryText))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(Interaction.<span style="color:#4070a0">Modality</span>.<span style="color:#4070a0">IMAGE</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="whipping-up-the-ui-with-javelit">Whipping up the UI with Javelit</h2>
<p>What makes this research frontend interesting isn&rsquo;t just the AI logic with the <strong>Gemini Interactions API</strong>,
it&rsquo;s how quickly you&rsquo;re able to whip up a functional web UI for it using <strong>Javelit</strong>,
without the hassle of a complicated web framework.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>I invite you to check out my other <a href="https://glaforge.dev/tags/javelit/">articles on Javelit</a> to learn more.</p>
      </div>
    </div><h3 id="the-rendering-loop-philosophy">The Rendering Loop Philosophy</h3>
<p>The core of Javelit is its <strong>rendering loop</strong>.
You provide a lambda that describes your UI (or a <code>main</code> method when running with the <code>javelit</code> command).
Every time an interaction occurs (a button click, a checkbox toggle&hellip;) it re-executes the UI code from top to bottom.</p>
<p>As <a href="https://bsky.app/profile/cyrilou242.bsky.social">Cyril de Catheu</a>
(the creator of Javelit) would put it, it&rsquo;s a bit like a video game loop.
A state update triggers a repaint.
But contrary to video games, catching input and state updates is done automatically by the Javelit components, and
the repaint isn&rsquo;t flying at 60fps, but only when there&rsquo;s a UI interaction.
Because the code runs top-to-bottom, you don&rsquo;t need to manage complex event listeners or manual DOM updates.</p>
<h2 id="conclusion">Conclusion</h2>
<p>By combining the power of specialized Gemini models and agents with the <strong>Gemini Interactions API</strong>
<em>(and my <a href="https://github.com/glaforge/gemini-interactions-api-sdk">Java SDK</a> for it)</em>
with the rapid UI development of <a href="https://javelit.io/">Javelit</a>,
I was able to build a sophisticated research tool,
taking advantage of the powerful <a href="https://ai.google.dev/gemini-api/docs/deep-research">Deep Research agent</a>,
in a fraction of the time it would take with a traditional frontend stack.</p>
<p>The ability to stream thoughts from the <a href="https://ai.google.dev/gemini-api/docs/deep-research">Deep Research agent</a>
directly into a reactive Javelit container makes the whole experience feel &ldquo;alive&rdquo; and transparent.</p>
<p>And <strong>everything in Java</strong>&hellip; No Python was harmed in this exercise! &#x1f603;</p>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p><a href="https://github.com/glaforge/gemini-interactions-api-sdk/blob/main/src/test/java/io/github/glaforge/gemini/interactions/ResearchFrontend.java">Full source code</a>
of this deep research frontend on GitHub.</p>
      </div>
    </div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Executable Markdown Files with gcli-mdrun &amp; Gemini CLI</title><link>https://glaforge.dev/posts/2026/01/26/executable-markdown-files-with-gcli-mdrun-and-gemini-cli/</link><pubDate>Mon, 26 Jan 2026 20:13:39 +0100</pubDate><guid>https://glaforge.dev/posts/2026/01/26/executable-markdown-files-with-gcli-mdrun-and-gemini-cli/</guid><description>&lt;p>Have you ever wanted to turn your cool LLM prompts &amp;amp; tools, research notes, automation ideas, or even a simple &amp;ldquo;todo&amp;rdquo; list into an executable script?
Inspired by a &lt;a href="https://news.ycombinator.com/item?id=46549444">HackerNews post&lt;/a> about &lt;strong>executable Markdown&lt;/strong>,
I&amp;rsquo;m happy to share &lt;strong>&lt;code>gcli-mdrun&lt;/code>&lt;/strong>, a smart little script that allows you to transform standard Markdown files
into executable scripts powered by &lt;a href="https://geminicli.com/">Gemini CLI&lt;/a>.&lt;/p>
&lt;p>This project allows you to create AI-driven automation, pipelines, and autonomous &lt;em>bots&lt;/em> using mere Markdown text files.
You can find the project on GitHub at &lt;a href="https://github.com/glaforge/gcli-mdrun">https://github.com/glaforge/gcli-mdrun&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>Have you ever wanted to turn your cool LLM prompts &amp; tools, research notes, automation ideas, or even a simple &ldquo;todo&rdquo; list into an executable script?
Inspired by a <a href="https://news.ycombinator.com/item?id=46549444">HackerNews post</a> about <strong>executable Markdown</strong>,
I&rsquo;m happy to share <strong><code>gcli-mdrun</code></strong>, a smart little script that allows you to transform standard Markdown files
into executable scripts powered by <a href="https://geminicli.com/">Gemini CLI</a>.</p>
<p>This project allows you to create AI-driven automation, pipelines, and autonomous <em>bots</em> using mere Markdown text files.
You can find the project on GitHub at <a href="https://github.com/glaforge/gcli-mdrun">https://github.com/glaforge/gcli-mdrun</a>.</p>
<h2 id="quick-start">Quick Start</h2>
<p>Imagine a file named <code>weather.md</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#000080;font-weight:bold">#!/usr/bin/env gemini-run
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>Use only the Google Search tool to find the answer to the question below:
</span></span><span style="display:flex;"><span>What is the weather like in Paris right now?
</span></span></code></pre></div><p>Run it like any other script:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod +x weather.md
</span></span><span style="display:flex;"><span>./weather.md
</span></span></code></pre></div><p>And you&rsquo;d get something like:</p>
<pre tabindex="0"><code>I will search for the current weather in Paris.
The current weather in Paris is cloudy with a temperature of 7 °C.
Humidity is at 84%. Wind is blowing from the Southeast at 11 km/h.
There is currently no precipitation.
</code></pre><p>The prompt (below the <em>shebang</em> line) is actually executed by <a href="https://geminicli.com/">Gemini CLI</a>.</p>
<h2 id="how-to-install">How to Install</h2>
<p>To get started, you need the <code>gemini-run</code> wrapper script from <a href="https://github.com/glaforge/gcli-mdrun">the <code>gcli-mdrun</code> epository</a>.</p>
<ol>
<li><strong>Download the script</strong>: You can find it in the <code>scripts/</code> directory of the repo.</li>
<li><strong>Install it</strong>: Make it executable and move it to your system path.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod +x gemini-run
</span></span><span style="display:flex;"><span>sudo mv gemini-run /usr/local/bin/
</span></span></code></pre></div>
            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Of course, you&rsquo;ll have to have <a href="https://geminicli.com/docs/get-started/installation/">Gemini CLI installed</a>,
and a valid <a href="https://ai.google.dev/gemini-api/docs/api-key">Gemini API key</a> configured as a <code>GEMINI_API_KEY</code> environment variable.</p>
      </div>
    </div><h2 id="usage--features">Usage &amp; Features</h2>
<h3 id="yolo-mode-autonomous-execution-aka-live-dangerously">YOLO Mode (Autonomous Execution, aka Live Dangerously)</h3>
<p>By using the Gemini CLI <code>--yolo</code> flag in the <em>shebang</em> of your markdown scripts,
Gemini will execute tools and commands automatically without asking for confirmation.</p>

    <div class="admonition danger">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>WARNING!</span>
      </div>
      <div class="admonition-content">
        <p>&#x26a0;&#xfe0f; <strong>Use with caution!</strong> &#x26a0;&#xfe0f;</p>
      </div>
    </div><div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#000080;font-weight:bold">#!/usr/bin/env -S gemini-run --yolo
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>List all files in the current directory and rename any file
</span></span><span style="display:flex;"><span>with a &#39;.txt&#39; extension to have a &#39;.bak&#39; extension instead.
</span></span></code></pre></div><h3 id="piping-and-pipelines">Piping and Pipelines</h3>
<p>Because <code>gemini-run</code> supports <em>stdin</em>, you can chain multiple markdown scripts together or mix them with standard Unix tools.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat customers.log | ./step1_extract.md | ./step2_analyze.md
</span></span></code></pre></div><p>And also redirect their outputs to files, with <code>&gt;</code>.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Those familiar with Gemini CLI <a href="https://geminicli.com/docs/cli/custom-commands/">custom commands</a> might find custom commands more useful
in particular for handling inputs or arguments, rather than piping script outputs.</p>
      </div>
    </div><h2 id="real-world-examples">Real-World Examples</h2>
<p>Here are some cool things you can do with <code>gcli-mdrun</code> (look at those 3 <a href="https://github.com/glaforge/gcli-mdrun/tree/main/examples">examples</a> from the repo:</p>
<h3 id="1-automated-release-notes-git-log-summarymd">1. Automated Release Notes (<code>git-log-summary.md</code>)</h3>
<p>This script analyzes your recent git commits (in the git project in the current folder) a
nd generates structured release notes.
It uses the <code>run_shell_command</code> tool to fetch git logs and diffs (using the <code>git</code> command).</p>
<p>I won&rsquo;t copy the whole script here as it&rsquo;s a bit too long, but I&rsquo;d like just to show you the <em>shebang</em> line:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">#!/usr/bin/env -S gemini-run --model gemini-2.5-flash --allowed-tools=run_shell_command(git)
</span></span></span></code></pre></div><p>Gemini CLI allows you to specify which tools to allow or forbid, which MCP servers to use or restrict, etc.
Here, I only allowed the execution of the <code>git</code> command via Gemini CLI&rsquo;s <code>run_shell_command</code> tool.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Be sure to check the documentation of Gemini CLI, and its flags, and ideally avoid giving too many permissions to your executable scripts.</p>
      </div>
    </div><p>For example, I applied the script to my <a href="https://github.com/glaforge/gcli-mdrun">gcli-mdrun</a> repository and got this
(after piping the output to the <a href="https://github.com/charmbracelet/glow">glow</a> Markdown highlighter):</p>
<p><figure>
  <a href="#img-b6e391c6502ce42176651020fab9a139">
    <img src="/img/gemini-cli/gcli-mdrun/release-notes.png"
      alt="Release notes for gcli-mdrun"
       />
  </a>
  <figcaption>Release notes for gcli-mdrun</figcaption>
</figure>
<div class="lightbox" id="img-b6e391c6502ce42176651020fab9a139">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/gcli-mdrun/release-notes.png"
    alt="Release notes for gcli-mdrun"
     />
  <div class="lightbox-caption">Release notes for gcli-mdrun</div>
</div>
</p>
<h3 id="2-intelligent-search-google-searchmd">2. Intelligent Search (<code>google-search.md</code>)</h3>
<p>Leverage the power of Google Search directly from your Markdown scripts.
This example fetches real-time information from the web, requesting the weather forecast for Paris:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#000080;font-weight:bold">#!/usr/bin/env -S gemini-run --allowed-tools=google_web_search,web_fetch
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>Use Google Search to find the answer to the question below.
</span></span><span style="display:flex;"><span>Don&#39;t use any other tools.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>What is the weather currently Paris?
</span></span><span style="display:flex;"><span>(be sure to use international units exclusively)
</span></span></code></pre></div>
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Notice how the tools are restricted with an <em>allow-list</em>.</p>
      </div>
    </div><h3 id="3-visual-summaries-with-nano-banana-nano-bananamd">3. Visual Summaries with Nano Banana (<code>nano-banana.md</code>)</h3>
<p>This is where it gets really creative. You can use the <code>nanobanana</code> MCP server
or <a href="https://github.com/gemini-cli-extensions/nanobanana">Nano Banana Gemini CLI extension</a> to generate infographics and mindmaps from articles or search results.</p>
<p>For example, I ran it against one of my recent articles:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#000080;font-weight:bold">#!/usr/bin/env -S gemini-run --allowed-mcp-server-names=nanobanana
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">*</span> Find the key points of the article at
</span></span><span style="display:flex;"><span>  https://glaforge.dev/posts/2026/01/03/building-a-research-assistant-with-the-interactions-api-in-java/
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">*</span> Make a sketchnote mindmap of the article, with pure white background.
</span></span><span style="display:flex;"><span>  Use highlighters to stress important keywords, and colored thick arrows for each section.
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">*</span> Display this infographic
</span></span></code></pre></div><p>And it generated this beautiful sketchnote mindmap:</p>
<p><figure>
  <a href="#img-49215dbbc3132926c209dfb2ad3a7f72">
    <img src="/img/gemini-cli/gcli-mdrun/mindmap.png"
      alt="Sketchnote mindmap of building a research assistant"
       />
  </a>
  <figcaption>Sketchnote mindmap of building a research assistant</figcaption>
</figure>
<div class="lightbox" id="img-49215dbbc3132926c209dfb2ad3a7f72">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/gcli-mdrun/mindmap.png"
    alt="Sketchnote mindmap of building a research assistant"
     />
  <div class="lightbox-caption">Sketchnote mindmap of building a research assistant</div>
</div>
</p>
<h3 id="4-chaining-search-and-graphics-nano-banana-chainmd">4. Chaining Search and Graphics (<code>nano-banana-chain.md</code>)</h3>
<p>You can even pipe the output of a search script into a graphics script.
For instance, getting the weather in Paris from the Google Search script we&rsquo;ve already seen,
and immediately generating a kawaii-style infographic of it by piping the weather output to a &#x1f34c; Nano Banana script&hellip;</p>
<p>&#x2601;&#xfe0f; And then you can get a super-cute output like this one to illustrate the weather! &#x2600;&#xfe0f;</p>
<p><figure>
  <a href="#img-5485ee269996005f7aefaf9fc0e54440">
    <img src="/img/gemini-cli/gcli-mdrun/weather.png"
      alt="Infographic of current weather in Paris"
       />
  </a>
  <figcaption>Infographic of current weather in Paris</figcaption>
</figure>
<div class="lightbox" id="img-5485ee269996005f7aefaf9fc0e54440">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/gcli-mdrun/weather.png"
    alt="Infographic of current weather in Paris"
     />
  <div class="lightbox-caption">Infographic of current weather in Paris</div>
</div>
</p>
<h2 id="conclusion">Conclusion</h2>
<p><code>gcli-mdrun</code> is all about making AI more accessible and integrable into your existing workflows.
By treating Markdown as code, we can bridge the gap between human-readable documentation and machine-executable tasks,
thanks to <a href="https://geminicli.com/">Gemini CLI</a> and a little bit of glue shell script.</p>
<p>Check out the project on <a href="https://github.com/glaforge/gcli-mdrun">GitHub</a>
and tell me what cool and handy executable Markdown scripts you&rsquo;ll create!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Implementing an arXiv MCP Server with Quarkus in Java</title><link>https://glaforge.dev/posts/2026/01/18/implementing-an-arxiv-mcp-server-with-quarkus-in-java/</link><pubDate>Sun, 18 Jan 2026 06:12:06 +0100</pubDate><guid>https://glaforge.dev/posts/2026/01/18/implementing-an-arxiv-mcp-server-with-quarkus-in-java/</guid><description>&lt;p>For my recent presentation at &lt;a href="https://snowcamp.io/">SnowCamp&lt;/a> on
&lt;a href="https://glaforge.dev/talks/2026/01/16/on-ai-standards-and-protocols-focus-on-mcp-and-a2a/">AI Standards &amp;amp; Protocols for AI Agents&lt;/a>,
I decided to &lt;strong>build an MCP server&lt;/strong> to access the &lt;a href="http://arxiv.org/">arXiv&lt;/a> research paper website
where pre-print versions are published and shared with the community.&lt;/p>
&lt;p>My goal was to shed light on some lesser-known aspects of the Model Context Protocol:&lt;/p>
&lt;ul>
&lt;li>&amp;#x1f6e0;&amp;#xfe0f; While the majority of MCP servers use the &lt;strong>tools&lt;/strong> feature to expose actions that LLMs can request to call,&lt;/li>
&lt;li>&amp;#x1f4c4; An MCP server can also share &lt;strong>resources&lt;/strong> (and resource templates), exposing various static assets the AI app might be interested in,&lt;/li>
&lt;li>&amp;#x270f;&amp;#xfe0f; And &lt;strong>prompts&lt;/strong> (and prompt templates) that users can access and reuse to utilize the MCP server effectively.&lt;/li>
&lt;/ul>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition info">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">&lt;path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>&lt;/svg>
&lt;span>Info&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>For the impatient, feel free to go straight to the
&lt;a href="https://github.com/glaforge/arxiv-mcp-server">GitHub repository&lt;/a> for the full source code.
The &lt;a href="https://github.com/glaforge/arxiv-mcp-server/blob/main/README.md">README.md&lt;/a> file
gives instructions on how to build, run, configure, and use the server.&lt;/p></description><content:encoded>
<![CDATA[<p>For my recent presentation at <a href="https://snowcamp.io/">SnowCamp</a> on
<a href="https://glaforge.dev/talks/2026/01/16/on-ai-standards-and-protocols-focus-on-mcp-and-a2a/">AI Standards &amp; Protocols for AI Agents</a>,
I decided to <strong>build an MCP server</strong> to access the <a href="http://arxiv.org/">arXiv</a> research paper website
where pre-print versions are published and shared with the community.</p>
<p>My goal was to shed light on some lesser-known aspects of the Model Context Protocol:</p>
<ul>
<li>&#x1f6e0;&#xfe0f; While the majority of MCP servers use the <strong>tools</strong> feature to expose actions that LLMs can request to call,</li>
<li>&#x1f4c4; An MCP server can also share <strong>resources</strong> (and resource templates), exposing various static assets the AI app might be interested in,</li>
<li>&#x270f;&#xfe0f; And <strong>prompts</strong> (and prompt templates) that users can access and reuse to utilize the MCP server effectively.</li>
</ul>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>For the impatient, feel free to go straight to the
<a href="https://github.com/glaforge/arxiv-mcp-server">GitHub repository</a> for the full source code.
The <a href="https://github.com/glaforge/arxiv-mcp-server/blob/main/README.md">README.md</a> file
gives instructions on how to build, run, configure, and use the server.</p>
      </div>
    </div><h2 id="the-combo-antigravity--quarkus--java">The Combo: Antigravity + Quarkus + Java</h2>
<p>To implement this server, I selected the <a href="https://quarkus.io/">Quarkus</a> framework (in Java)
and its <a href="https://quarkus.io/extensions/?search-regex=mcp">extensive MCP support</a>
(documented <a href="https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html">here</a>).</p>
<p>I enlisted <a href="https://antigravity.google/">Antigravity</a> to help me in this adventure.
I pointed my agentic IDE to the <a href="https://info.arxiv.org/help/api/user-manual.html">arXiv API User&rsquo;s Manual</a>
to draft and scaffold my project, and iteratively collaborated with it to expand the coverage of the arXiv API.
It was a pretty productive session! <strong>I highly recommend checking out Antigravity</strong>!</p>
<h2 id="a-look-at-the-arxiv-api">A Look at the arXiv API</h2>
<p>PDFs are accessible at a URL of the form <code>https://arxiv.org/pdf/{paperID}</code>.</p>
<p>The <a href="https://info.arxiv.org/help/api/user-manual.html">arXiv API</a> offers programmatic access to e-prints via HTTP requests
with parameters for searches, specific paper IDs, pagination, and sorting.
Users can build complex queries using boolean operators, phrases, and grouping.</p>
<p>So for searching, I&rsquo;ve created a REST client that I used in my MCP server implementation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@RegisterRestClient</span>(baseUri<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;https://export.arxiv.org/api&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@RegisterProvider</span>(ArxivResponseFilter.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">ArxivClient</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@GET</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Path</span>(<span style="color:#4070a0">&#34;/query&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Produces</span>(<span style="color:#4070a0">&#34;*/*&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">search</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@QueryParam</span>(<span style="color:#4070a0">&#34;search_query&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>searchQuery,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@QueryParam</span>(<span style="color:#4070a0">&#34;id_list&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>idList,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@QueryParam</span>(<span style="color:#4070a0">&#34;start&#34;</span>)<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>start,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@QueryParam</span>(<span style="color:#4070a0">&#34;max_results&#34;</span>)<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>maxResults,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@QueryParam</span>(<span style="color:#4070a0">&#34;sortBy&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>sortBy,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@QueryParam</span>(<span style="color:#4070a0">&#34;sortOrder&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>sortOrder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Unusually, the arXiv API returns results in <strong>Atom 1.0 XML format</strong> (rather than the typical JSON),
providing detailed metadata for both the query and individual articles, including titles, abstracts, authors, categories, and links.</p>
<p>In order to parse the Atom format and map the feeds to Java classes, I simply went with Jackson&rsquo;s XML parser
(perhaps I could have used the venerable <a href="https://github.com/rometools/rome">Rome</a> project).</p>
<p>Here&rsquo;s one of the entities used in the ATOM domain model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@JsonIgnoreProperties</span>(ignoreUnknown<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">Entry</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@JacksonXmlProperty</span>(namespace<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;http://www.w3.org/2005/Atom&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>id;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@JacksonXmlProperty</span>(namespace<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;http://www.w3.org/2005/Atom&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>published;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@JacksonXmlProperty</span>(namespace<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;http://www.w3.org/2005/Atom&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>title;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@JacksonXmlProperty</span>(namespace<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;http://www.w3.org/2005/Atom&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>summary;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@JacksonXmlElementWrapper</span>(useWrapping<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@JacksonXmlProperty</span>(localName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;author&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>namespace<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;http://www.w3.org/2005/Atom&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>Author<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>authors;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p><em>(It is somewhat ironic to use the <code>@JsonIgnoreProperties</code> annotation when parsing XML!)</em></p>
<h2 id="lets-start-with-tools">Let&rsquo;s Start with Tools</h2>
<p>What I like about the Quarkus MCP support is that to turn a Quarkus app into an MCP server,
you just need a few Java annotations, and everything is handled for you!</p>
<p>In my <code>ArxivMcpServer</code>, I injected my <code>ArxivClient</code> REST client:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">ArxivMcpServer</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Inject</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@RestClient</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ArxivClient<span style="color:#bbb"> </span>arxivClient;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then to define a tool, I used the <code>@Tool</code> annotation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Java" data-lang="Java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Search for papers on arXiv&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;search_papers&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Feed<span style="color:#bbb"> </span><span style="color:#06287e">searchPapers</span>(String<span style="color:#bbb"> </span>query,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>maxResults,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                         </span>SortBy<span style="color:#bbb"> </span>sortBy,<span style="color:#bbb"> </span>SortOrder<span style="color:#bbb"> </span>sortOrder)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>performSearch(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>query,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span>0,<span style="color:#bbb"> </span>maxResults<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span>0<span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span>5<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>maxResults,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>sortBy<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>sortBy.<span style="color:#4070a0">name</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>sortOrder<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>sortOrder.<span style="color:#4070a0">name</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I could have added <code>@ToolArg</code> annotations on the parameters of this method to give the LLM more context on their role, but the parameter names were self-explanatory.</p>
<h2 id="expose-the-taxonomy-as-a-resource">Expose the Taxonomy as a Resource</h2>
<p>For well-known static assets like the taxonomy of all the domain categories of research papers,
you can expose an <strong>MCP Resource</strong>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Resource</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>uri<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;arxiv://taxonomy&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;List of arXiv categories and their codes&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;text/markdown&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>TextResourceContents<span style="color:#bbb"> </span><span style="color:#06287e">getTaxonomy</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>TextResourceContents.<span style="color:#4070a0">create</span>(<span style="color:#4070a0">&#34;arxiv://taxonomy&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        # arXiv Category Taxonomy
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Computer Science (cs)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   **Artificial Intelligence** (cs.AI)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   **Computation and Language** (cs.CL)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   **Computer Vision** (cs.CV)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   **Machine Learning** (cs.LG)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   **Robotics** (cs.RO)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   **Software Engineering** (cs.SE)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        *   ... and many more.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Physics
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ...
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Again, an annotation, <code>@Resource</code>, is all that&rsquo;s needed to define a resource.
We give it a URI, a description, and a MIME type.</p>
<p>Resources are either text content or binary content.
So depending on the type of your resource,
your methods can return either <code>TextResourceContents</code> or <code>BlobResourceContents</code>.
Here, for the taxonomy, it&rsquo;s just Markdown text.</p>
<p>Since there is only one known taxonomy, the resource name is static and explicit.
However, you can also take advantage of <strong>MCP Resource Templates</strong> which support parameterization.</p>
<p>This is the case, for example, when accessing metadata for each paper.
So I created a resource template as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@ResourceTemplate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>uriTemplate<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;arxiv://papers/{id}/metadata&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;The full metadata of the arXiv paper&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>TextResourceContents<span style="color:#bbb"> </span><span style="color:#06287e">getMetadata</span>(<span style="color:#555;font-weight:bold">@ResourceTemplateArg</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>id)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Feed<span style="color:#bbb"> </span>feed<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>performSearch(<span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span>id,<span style="color:#bbb"> </span>0,<span style="color:#bbb"> </span>1,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(feed.<span style="color:#4070a0">entries</span><span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>feed.<span style="color:#4070a0">entries</span>.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>TextResourceContents.<span style="color:#4070a0">create</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;arxiv://papers/&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>id<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;/metadata&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>jsonMapper.<span style="color:#4070a0">writeValueAsString</span>(feed.<span style="color:#4070a0">entries</span>.<span style="color:#4070a0">get</span>(0)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(JsonProcessingException<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>RuntimeException(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;Failed to serialize paper metadata&#34;</span>,<span style="color:#bbb"> </span>e);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>RuntimeException(<span style="color:#4070a0">&#34;Paper not found: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>id);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I used an <code>@ResourceTemplate</code> annotation, and the <code>uri</code> parameter is replaced with <code>uriTemplate</code> which contains a placeholder for the ID of the paper.
That paper ID is actually passed as a parameter to the method, and this parameter is annotated with a <code>@ResourceTemplateArg</code> annotation.</p>
<p>Again for templates, it&rsquo;s like for plain resources, you either return <code>TextResourceContents</code> or <code>BlobResourceContents</code> for binary content.</p>
<h2 id="prepare-reusable-prompts-for-the-user">Prepare Reusable Prompts for the User</h2>
<p><strong>MCP Prompts</strong> are prompts for the user to use to make the best possible use of the MCP server.</p>
<p>Here&rsquo;s a method returning a prompt to get summaries of papers:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Prompt</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;summarize_paper&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Summarize the given paper&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>PromptMessage<span style="color:#bbb"> </span><span style="color:#06287e">summarizePaper</span>(String<span style="color:#bbb"> </span>id)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Feed<span style="color:#bbb"> </span>feed<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>performSearch(<span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span>id,<span style="color:#bbb"> </span>0,<span style="color:#bbb"> </span>1,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(feed.<span style="color:#4070a0">entries</span><span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>feed.<span style="color:#4070a0">entries</span>.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>summary<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>feed.<span style="color:#4070a0">entries</span>.<span style="color:#4070a0">get</span>(0).<span style="color:#4070a0">summary</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>PromptMessage.<span style="color:#4070a0">withUserRole</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                Please summarize this paper abstract (ID: %s):
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                %s&#34;&#34;&#34;</span>,<span style="color:#bbb"> </span>id,<span style="color:#bbb"> </span>summary));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>PromptMessage.<span style="color:#4070a0">withUserRole</span>(<span style="color:#4070a0">&#34;Error: Paper not found&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The method returns a <code>PromptMessage</code> with the <strong>user</strong> role, as it is a prompt for the user.</p>
<p>This is a simple prompt that summarizes the paper&rsquo;s abstract (a summary of a summary).
Abstracts can be overly <em>scientific</em> and hard for non-experts to decipher.
However, this simple prompt usually yields easy-to-understand summaries.
Of course, you might instead retrieve the whole paper and create a much more elaborate summary that analyzes the whole content instead of just the abstract.</p>
<p>Perhaps more interesting is the prompt I defined to help craft search queries:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Prompt</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;construct_search_query&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Helper to construct an arXiv search query&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>PromptMessage<span style="color:#bbb"> </span><span style="color:#06287e">constructSearchQuery</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@PromptArg</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Topic or keywords&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>topic,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@PromptArg</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Author name&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>author,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@PromptArg</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Category code (e.g. cs.AI)&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>category,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@PromptArg</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Year (e.g. 2024)&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>year)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I&rsquo;m just showing the signature here, but notice that this time I used <code>@PromptArg</code> annotations to give more details about each argument.</p>
<h2 id="configuration-in-gemini-cli">Configuration in Gemini CLI</h2>
<p>For using this MCP server in your favorite MCP client, you&rsquo;ll have to configure it to point at this Quarkus application.
MCP servers can be either local <em>STDIO</em> servers that run along your application (they are actually launched by your client, and use standard in and out for communication),
or they can be <em>remote</em> by using a <em>Streamable HTTP</em> transport mechanism (the server could be running locally as well, or be deployed in the cloud).</p>
<p>One cool thing with the Quarkus MCP extension is that choosing between STDIO and Streamable HTTP is <strong>just</strong> a build dependency change.
And if you want, you can build your MCP server to support both transports by using both dependencies in your build.
Here, for example with Maven&rsquo;s <code>pom.xml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.quarkiverse.mcp<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>quarkus-mcp-server-http<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.8.1<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.quarkiverse.mcp<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>quarkus-mcp-server-stdio<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.8.1<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>For coding and plenty other automation tasks, I tend to use <a href="https://geminicli.com/">Gemini CLI</a>,
but the syntax should be similar for your favorite chat / coding agent.</p>
<p>In development mode, I was actually running my MCP server as a Streamable HTTP server on the same host.
So I was simply running Quarkus in development mode with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./mvnw quarkus:dev
</span></span></code></pre></div><p>And then I pointed the Gemini CLI at the local URL in my <code>~/.gemini/settings.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;arxiv&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;httpUrl&#34;</span>: <span style="color:#4070a0">&#34;http://localhost:8080/mcp&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>But once I&rsquo;m happy with the development, I install the application in my Maven local repository (or elsewhere),
and then I configure the MCP server to point at the absolute path where the JAR was installed:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;arxiv&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;command&#34;</span>: <span style="color:#4070a0">&#34;java&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;args&#34;</span>: [<span style="color:#4070a0">&#34;-jar&#34;</span>, <span style="color:#4070a0">&#34;/absolute/path/to/quarkus-run.jar&#34;</span>]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Of course, if I decided to deploy my arXiv server to <a href="https://cloud.run/">Cloud Run</a>, for example, I would use the <code>httpUrl</code> parameter configuration approach instead.</p>
<h2 id="lets-search-papers">Let&rsquo;s Search Papers!</h2>
<p>When I run the <code>/mcp list</code> command inside Gemini CLI, I see my tools, resources, and prompts are properly exposed:</p>
<p><figure>
  <a href="#img-1627e2c28242d78d8c53881352983576">
    <img src="/img/gemini-cli/mcp-server/arxiv-mcp-0.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1627e2c28242d78d8c53881352983576">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/mcp-server/arxiv-mcp-0.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Resource templates are currently not supported by Gemini CLI, so they don&rsquo;t <em>yet</em> appear, but soon will, hopefully! Stay tuned.</p>
      </div>
    </div><p>I asked <code>what are the latest 10 papers in artificial intelligence (sorted by publication date)?</code> and you can see that it invoked my arXiv server
and its <code>search_papers</code> tool with the following parameters: <code>{&quot;sortOrder&quot;: &quot;descending&quot;, &quot;sortBy&quot;: &quot;lastUpdatedDate&quot;, &quot;query&quot;: &quot;cat:cs.AI&quot;, &quot;maxResults&quot;: 10}</code>.</p>
<p><figure>
  <a href="#img-2e044359d3243fdf677f6c9a236fd9c2">
    <img src="/img/gemini-cli/mcp-server/arxiv-mcp-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-2e044359d3243fdf677f6c9a236fd9c2">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/mcp-server/arxiv-mcp-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then Gemini synthesized a human-readable interpretation of those JSON search results:</p>
<p><figure>
  <a href="#img-f21b2f52560435f7023e2327f2b538fb">
    <img src="/img/gemini-cli/mcp-server/arxiv-mcp-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f21b2f52560435f7023e2327f2b538fb">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/mcp-server/arxiv-mcp-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I asked for more details about #2 and #7 of the list with this query: <code>I'd like to learn more about #2 and #7</code></p>
<p><figure>
  <a href="#img-9c8daf1d0e601b78a3c21c08ecbd36ed">
    <img src="/img/gemini-cli/mcp-server/arxiv-mcp-3.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9c8daf1d0e601b78a3c21c08ecbd36ed">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/mcp-server/arxiv-mcp-3.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And the <code>get_paper_details</code> MCP tool was invoked with the paper IDs (<code>{&quot;ids&quot;: [&quot;2601.10702&quot;, &quot;2601.10679&quot;]}</code>):</p>
<p><figure>
  <a href="#img-93b23d7460b17680f7ba18ae171a0e81">
    <img src="/img/gemini-cli/mcp-server/arxiv-mcp-4.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-93b23d7460b17680f7ba18ae171a0e81">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/mcp-server/arxiv-mcp-4.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Finally Gemini gave me a bit more context about those two papers:</p>
<p>And voila!</p>
<h2 id="summary">Summary</h2>
<p>In this post, we&rsquo;ve seen how the combination of <strong>Quarkus</strong> and its <strong>MCP extension</strong>
makes it straightforward to build a feature-complete MCP server in <strong>Java</strong>.
By leveraging annotations, we easily exposed not just <strong>tools</strong>, but also <strong>resources</strong>
(for taxonomy and metadata) and <strong>prompts</strong> (to guide the user), providing a rich context for any AI agent.</p>
<p>The development process was also a great example of <strong>AI-assisted productivity</strong>:
using <strong>Antigravity</strong> to scaffold the project and handle the integration with the arXiv API significantly sped up the implementation.
Whether you choose to run your server via <strong>STDIO</strong> for local use or <strong>HTTP</strong> for remote access,
the Model Context Protocol opens up exciting possibilities for making your data and services &ldquo;AI-ready.&rdquo;</p>
<p>Feel free to explore the <a href="https://github.com/glaforge/arxiv-mcp-server">source code on GitHub</a> and start building your own MCP servers!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>On AI Standards and Protocols: Focus on MCP and A2A</title><link>https://glaforge.dev/talks/2026/01/16/on-ai-standards-and-protocols-focus-on-mcp-and-a2a/</link><pubDate>Fri, 16 Jan 2026 11:04:42 +0100</pubDate><guid>https://glaforge.dev/talks/2026/01/16/on-ai-standards-and-protocols-focus-on-mcp-and-a2a/</guid><description>&lt;p>At &lt;a href="https://snowcamp.io/">SnowCamp 2026&lt;/a>,
with my &lt;a href="https://lescastcodeurs.com/">Cast Codeurs&lt;/a>
buddy &lt;a href="https://x.com/emmanuelbernard">Emmanuel Bernard&lt;/a>
of &lt;a href="https://hexactgon.com/">Hexactgon&lt;/a>,
I had the chance to deliver a talk on AI standards and protocols,
with a big focus on &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP&lt;/a> (Model Context Protocol),
and &lt;a href="https://a2a-protocol.org/latest/">A2A&lt;/a> (Agent 2 Agent Protocol).&lt;/p>
&lt;p>Without further ado, here&amp;rsquo;s the slide deck we presented:&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="c93dff0ec41f47a693a02b9c2402d189" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>This talk is based on the Devoxx 2025
&lt;a href="https://m.devoxx.com/events/dvbe25/talks/24587/on-standards-and-ai-agents-a-walkthrough-of-mcp-a2a-adk-and-more">deep dive session&lt;/a>
that I delivered with Emmanuel and my colleague &lt;a href="https://atamel.dev/">Mete Atamel&lt;/a>.
As the talk wasn&amp;rsquo;t recorded during SnowCamp, I&amp;rsquo;ll share with you the 3h-long video from Devoxx below:&lt;/p></description><content:encoded>
<![CDATA[<p>At <a href="https://snowcamp.io/">SnowCamp 2026</a>,
with my <a href="https://lescastcodeurs.com/">Cast Codeurs</a>
buddy <a href="https://x.com/emmanuelbernard">Emmanuel Bernard</a>
of <a href="https://hexactgon.com/">Hexactgon</a>,
I had the chance to deliver a talk on AI standards and protocols,
with a big focus on <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> (Model Context Protocol),
and <a href="https://a2a-protocol.org/latest/">A2A</a> (Agent 2 Agent Protocol).</p>
<p>Without further ado, here&rsquo;s the slide deck we presented:</p>
<script async class="speakerdeck-embed" data-id="c93dff0ec41f47a693a02b9c2402d189" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>This talk is based on the Devoxx 2025
<a href="https://m.devoxx.com/events/dvbe25/talks/24587/on-standards-and-ai-agents-a-walkthrough-of-mcp-a2a-adk-and-more">deep dive session</a>
that I delivered with Emmanuel and my colleague <a href="https://atamel.dev/">Mete Atamel</a>.
As the talk wasn&rsquo;t recorded during SnowCamp, I&rsquo;ll share with you the 3h-long video from Devoxx below:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/DiZs--ODXVM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="abstract">Abstract</h2>
<blockquote>
<p>AI agent foundations are built over a handful of common protocols that you need to master,
to make the best out of your LLM and agent framework. That’s why it’s important to understand them.
But some are catching up, others are not.</p>
<p>In this deep dive, we will explore the ecosystem showing you these standards and focusing on the important ones.
Knowing some of the frameworks is useful too to get started faster.
Welcome MCP, A2A, ACP protocols, and ADK, Arc, Quarkus, LangChain4j frameworks!</p>
<p>After giving you an overview of the main standards and protocols, their merit and their popularity,
we will start by building an agent using Agent Development Kit (ADK) and walk through making a tool call.
From there, zooming on MCP, we’ll see how to standardize that tool via a local MCP server
and then deploy it as a remote MCP server to share it with others.</p>
<p>Next, we’ll dive into the A2A protocol and enable our agent to participate in multi-agent conversations.
And to do that, we will use another framework, Quarkus and LangChain4j, showing how different stacks interact seamlessly through A2A.</p>
<p>You’ll learn not just what these protocols do and how they work, but why they matter, with detailed walkthroughs and live demos throughout.</p>
<p>If you’re struggling to understand all the protocol details around AI agents, this session is for you!</p></blockquote>
<h2 id="important-links">Important links</h2>
<p>Throughout the presentation, we showed various demos, implemented in Java:</p>
<h3 id="mcp">MCP</h3>
<ul>
<li><a href="https://github.com/glaforge/mn-mcp-server">Micronaut moon phases MCP server</a></li>
<li><a href="https://github.com/glaforge/moon-phases-quarkus-mcp-sse-server">Quarkus moon phases MCP server</a></li>
<li><a href="https://github.com/glaforge/arxiv-mcp-server">arXiv papers MCP server</a></li>
<li><a href="https://github.com/meteatamel/genai-beyond-basics">Python samples from Mete Atamel</a> showed at Devoxx</li>
</ul>
<h3 id="a2a">A2A</h3>
<ul>
<li><a href="https://github.com/glaforge/ai-agent-protocols">My repository with the product marketing MCP+A2A demo and other ADK samples</a> (showed at Devoxx)</li>
<li><a href="https://github.com/emmanuelbernard/quarkus-a2a-deepdive/tree/snowcamp-2026">Emmanuel&rsquo;s Quarkus A2A demo</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Building a Research Assistant with the Interactions API in Java</title><link>https://glaforge.dev/posts/2026/01/03/building-a-research-assistant-with-the-interactions-api-in-java/</link><pubDate>Sat, 03 Jan 2026 11:59:37 +0100</pubDate><guid>https://glaforge.dev/posts/2026/01/03/building-a-research-assistant-with-the-interactions-api-in-java/</guid><description>&lt;p>First of all, dear readers, let me wish you a &lt;strong>happy new year&lt;/strong>!
This is my first post on this blog for 2026.
I&amp;rsquo;m looking forward to continuing sharing interesting content with you.&lt;/p>
&lt;p>During my holiday break, I wanted to put my recent
&lt;a href="https://glaforge.dev/posts/2025/12/15/implementing-the-interactions-api-with-antigravity/">Java implementation&lt;/a>
of the Gemini &lt;a href="https://developers.googleblog.com/building-agents-with-the-adk-and-the-new-interactions-api/">Interactions API&lt;/a>
to the test. I implemented and released it with the help of &lt;a href="https://antigravity.google/">Antigravity&lt;/a>.
My colleague &lt;a href="https://x.com/Saboo_Shubham_">Shubham Saboo&lt;/a> and Gargi Gupta wrote a tutorial on
how to &lt;a href="https://www.theunwindai.com/p/build-an-ai-research-agent-with-google-interactions-api-gemini-3">build an AI research agent with Google Interactions API &amp;amp; Gemini 3&lt;/a>.
I thought this was a great opportunity to replicate this example in Java
using my &lt;a href="https://github.com/glaforge/gemini-interactions-api-sdk/">Interactions API Java SDK&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>First of all, dear readers, let me wish you a <strong>happy new year</strong>!
This is my first post on this blog for 2026.
I&rsquo;m looking forward to continuing sharing interesting content with you.</p>
<p>During my holiday break, I wanted to put my recent
<a href="https://glaforge.dev/posts/2025/12/15/implementing-the-interactions-api-with-antigravity/">Java implementation</a>
of the Gemini <a href="https://developers.googleblog.com/building-agents-with-the-adk-and-the-new-interactions-api/">Interactions API</a>
to the test. I implemented and released it with the help of <a href="https://antigravity.google/">Antigravity</a>.
My colleague <a href="https://x.com/Saboo_Shubham_">Shubham Saboo</a> and Gargi Gupta wrote a tutorial on
how to <a href="https://www.theunwindai.com/p/build-an-ai-research-agent-with-google-interactions-api-gemini-3">build an AI research agent with Google Interactions API &amp; Gemini 3</a>.
I thought this was a great opportunity to replicate this example in Java
using my <a href="https://github.com/glaforge/gemini-interactions-api-sdk/">Interactions API Java SDK</a>.</p>
<p>A picture is often worth a thousand words, so let&rsquo;s have a look at the key components of our research agent workflow:</p>
<p><figure>
  <a href="#img-a557bc150ed9e4d9cf74a179dc6906cf">
    <img src="/img/gemini/interactions/research_agent_workflow_infographic.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a557bc150ed9e4d9cf74a179dc6906cf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/research_agent_workflow_infographic.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The goal of this tutorial is to build a research assistant, split into 4 key phases:</p>
<ul>
<li>Phase &#x31;&#xfe0f;&#x20e3; : <strong>Planning</strong> — Given a topic to research, aided by the Google Search tool, Gemini 3 Flash defines different research tasks related to the topic.</li>
<li>Phase &#x32;&#xfe0f;&#x20e3; : <strong>Research</strong> — The Deep Research model will be launched as a background task to research the different topic areas defined in the planning phase.</li>
<li>Phase &#x33;&#xfe0f;&#x20e3; : <strong>Synthesis</strong> — This time, we use the more powerful Gemini 3 Pro to do the synthesis of the research report.</li>
<li>Phase &#x34;&#xfe0f;&#x20e3; : <strong>Infographic</strong> — Last but not least, we&rsquo;ll use &#x1f34c; Nano Banana Pro (aka Gemini 3 Pro Image) to generate an infographic about this research.</li>
</ul>
<h2 id="lets-implement-this-research-workflow">Let&rsquo;s Implement this Research Workflow!</h2>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Information</span>
      </div>
      <div class="admonition-content">
        <p>You&rsquo;ll find the entire source code for this <a href="https://github.com/glaforge/gemini-interactions-api-sdk/blob/main/src/test/java/io/github/glaforge/gemini/interactions/ResearchAgentTest.java">example in my GitHub repository</a>.</p>
      </div>
    </div><p>This all starts with the planning phase, using Gemini 3 Flash:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Step 0: Define the research goal</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>researchGoal<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Research the current state of Quantum Computing in 2025,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    specifically looking for major breakthroughs in error correction.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// --- Phase 1: Plan ---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Gemini 3 Flash Preview creates research tasks</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ModelInteractionParams<span style="color:#bbb"> </span>planParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-flash-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Create a numbered research plan for: %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Format: 1. [Task] - [Details]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Include 3 specific tasks.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>,<span style="color:#bbb"> </span>researchGoal))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearch())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">store</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Launch the request</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>planInteraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(planParams);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Retrieve the response with text and interaction ID</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>planText<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>getText(planInteraction);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>planId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>planInteraction.<span style="color:#4070a0">id</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>The goal is to research information about the latest breakthroughs in Quantum Computing over the past year.
We create an <em>interaction</em> that asks Gemini 3 Flash to define a few research tasks, following a specific format.</p>
<p>Notice that we provide the built-in <code>GoogleSearch</code> tool so the model can search the internet when defining those tasks.</p>
<p>We set <code>store(true)</code> to save the interaction on the server-side,
and we save the interaction ID for later reuse, ensuring subsequent interactions continue the same discussion.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Utility method to extract the LLM generated tasks</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>tasks<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>parseTasks(planText);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// --- Phase 2: Research ---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Select tasks and run Deep Research Agent</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// In this test, we select all tasks.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>selectedTasks<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>String.<span style="color:#4070a0">join</span>(<span style="color:#4070a0">&#34;\n\n&#34;</span>,<span style="color:#bbb"> </span>tasks);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>AgentInteractionParams<span style="color:#bbb"> </span>researchParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AgentInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">agent</span>(<span style="color:#4070a0">&#34;deep-research-pro-preview-12-2025&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;Research these tasks thoroughly with sources:\n\n%s&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>selectedTasks))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">previousInteractionId</span>(planId)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">background</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">store</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>researchInteraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(researchParams);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>researchId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>researchInteraction.<span style="color:#4070a0">id</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>researchText<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>getText(researchInteraction);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Wait for completion up to 10 mins as deep research can be slow</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>researchInteraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>waitForCompletion(client,<span style="color:#bbb"> </span>researchId,<span style="color:#bbb"> </span>600);<span style="color:#bbb">
</span></span></span></code></pre></div><p>In this second phase, I use a few utility methods to parse the tasks generated in the previous phase
(I should probably use structured output for that at some point)
and to wait for the completion of the background tasks.</p>
<p>A few interesting points here:</p>
<ul>
<li>We reuse the interaction ID from the previous phase via <code>previousInteractionId()</code>, taking advantage of the stateful nature of the Interactions API.
This allows the research agent to maintain context from the planning phase, in addition to the specific tasks generated.</li>
<li>We specify that the task should run in the background with <code>background(true)</code>.</li>
<li>Finally, we poll for the completion of the Deep Research agent, which can take several minutes.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// --- Phase 3: Synthesis ---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ModelInteractionParams<span style="color:#bbb"> </span>synthesisParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-pro-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Create executive report with Summary, Findings,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Recommendations, Risks based on the research.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">previousInteractionId</span>(researchId)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">store</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>synthesisInteraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(synthesisParams);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>synthesisText<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>getText(synthesisInteraction);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Phase 3 is a simple call to Gemini 3 Pro to synthesize the research report.
Again, we store the session and reuse the previous interaction ID.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// --- Phase 4: Infographic ---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ModelInteractionParams<span style="color:#bbb"> </span>infographicParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-pro-image-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;Create a whiteboard summary infographic for the following: \n\n&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#666">+</span><span style="color:#bbb"> </span>synthesisText)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseModalities</span>(List.<span style="color:#4070a0">of</span>(Modality.<span style="color:#4070a0">IMAGE</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>infographicInteraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(infographicParams);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>saveInfographic(infographicInteraction);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The last phase is the infographic generation, using &#x1f34c; Nano Banana Pro.
We pass the synthesis from the previous phase.
We don&rsquo;t need to reuse the interaction ID here, as the synthesis itself provides enough context for the infographic.</p>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Reminder</span>
      </div>
      <div class="admonition-content">
        <p>You can check
<a href="https://github.com/glaforge/gemini-interactions-api-sdk/blob/main/src/test/java/io/github/glaforge/gemini/interactions/ResearchAgentTest.java">the entire source code on GitHub</a>.</p>
      </div>
    </div><h2 id="the-outcome">The Outcome</h2>
<p>I won&rsquo;t include the full output, but I&rsquo;d like to highlight the impressive infographic generated by Nano Banana Pro as a result of this research plan:</p>
<p><figure>
  <a href="#img-54823401c91d43e20b06a4c058df3c95">
    <img src="/img/gemini/interactions/quantum-infographic.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-54823401c91d43e20b06a4c058df3c95">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/interactions/quantum-infographic.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I hope this makes sense to those of you knowledgeable about Quantum Computing.
While I&rsquo;m not an expert in the field, I&rsquo;m really fond of the infographics that &#x1f34c; Nano Banana can generate, with their sharp and crisp text.</p>
<h2 id="whats-to-like">What&rsquo;s to Like?</h2>
<p>What I particularly like about the Interactions API is that it handles state on the server-side.
It&rsquo;s a departure from the traditional stateless LLM conversations where frameworks must pass the entire history at each round.
Even unrelated LLM requests or agent tasks can share the same <em>session</em> by reusing the interaction ID.</p>
<p>Additionally, I&rsquo;m happy that my <a href="https://github.com/glaforge/gemini-interactions-api-sdk/">Java SDK</a>
for the Interactions API works well for more involved use cases, validating its capabilities.
Until the Gemini <em>unified</em> <a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/sdks/overview">SDK</a>
supports the Interaction API, I&rsquo;ll definitely be sticking with my own!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Implementing the Interactions API with Antigravity</title><link>https://glaforge.dev/posts/2025/12/15/implementing-the-interactions-api-with-antigravity/</link><pubDate>Mon, 15 Dec 2025 10:40:48 +0100</pubDate><guid>https://glaforge.dev/posts/2025/12/15/implementing-the-interactions-api-with-antigravity/</guid><description>&lt;p>Google and DeepMind have announced the &lt;strong>Interactions API&lt;/strong>, a new way to interact with Gemini models and agents.&lt;/p>
&lt;p>Here are some useful links to learn more about this new API:&lt;/p>
&lt;ul>
&lt;li>An announcement is available on Google&amp;rsquo;s Keywords blog:&lt;br />
&lt;a href="https://blog.google/technology/developers/interactions-api/">Interactions API: A unified foundation for models and agents&lt;/a>&lt;/li>
&lt;li>A more detailed article is available on Google&amp;rsquo;s developers blog:&lt;br />
&lt;a href="https://developers.googleblog.com/building-agents-with-the-adk-and-the-new-interactions-api/">Building agents with the ADK and the new Interactions API&lt;/a>&lt;/li>
&lt;li>The newly released Gemini &lt;strong>Deep Research agent&lt;/strong> is now available via the Interactions API as well:&lt;br />
&lt;a href="https://blog.google/technology/developers/deep-research-agent-gemini-api/">Build with Gemini Deep Research&lt;/a>&lt;/li>
&lt;li>The official &lt;a href="https://ai.google.dev/gemini-api/docs/interactions">documentation of the Interactions API&lt;/a>.&lt;/li>
&lt;/ul>
&lt;h2 id="about-the-interactions-api">About the Interactions API&lt;/h2>
&lt;h3 id="the-rationale-and-motivation">The Rationale and Motivation&lt;/h3>
&lt;p>The Interactions API was introduced to address a shift in AI development, moving from simple,
stateless text generation to more complex, multi-turn &lt;em>agentic&lt;/em> workflows.
It serves as a dedicated interface for systems that require memory, reasoning, and tool use.
It provides a unified interface for both simple LLM calls and more complex agent calls.&lt;/p></description><content:encoded>
<![CDATA[<p>Google and DeepMind have announced the <strong>Interactions API</strong>, a new way to interact with Gemini models and agents.</p>
<p>Here are some useful links to learn more about this new API:</p>
<ul>
<li>An announcement is available on Google&rsquo;s Keywords blog:<br />
<a href="https://blog.google/technology/developers/interactions-api/">Interactions API: A unified foundation for models and agents</a></li>
<li>A more detailed article is available on Google&rsquo;s developers blog:<br />
<a href="https://developers.googleblog.com/building-agents-with-the-adk-and-the-new-interactions-api/">Building agents with the ADK and the new Interactions API</a></li>
<li>The newly released Gemini <strong>Deep Research agent</strong> is now available via the Interactions API as well:<br />
<a href="https://blog.google/technology/developers/deep-research-agent-gemini-api/">Build with Gemini Deep Research</a></li>
<li>The official <a href="https://ai.google.dev/gemini-api/docs/interactions">documentation of the Interactions API</a>.</li>
</ul>
<h2 id="about-the-interactions-api">About the Interactions API</h2>
<h3 id="the-rationale-and-motivation">The Rationale and Motivation</h3>
<p>The Interactions API was introduced to address a shift in AI development, moving from simple,
stateless text generation to more complex, multi-turn <em>agentic</em> workflows.
It serves as a dedicated interface for systems that require memory, reasoning, and tool use.
It provides a unified interface for both simple LLM calls and more complex agent calls.</p>
<p>If you&rsquo;ve used the Gemini API before, the standard operation (<code>generateContent</code>) was designed for simple request-response tasks.
It was stateless, requiring you to send the entire conversation history with each new question.
As models evolved to incorporate <em>&ldquo;thinking&rdquo;</em> processes and advanced tool use (e.g., built-in tools, sequential and parallel function calls),
the classic API approach required extra fields to manage state, such as <em>&ldquo;thought signatures&rdquo;</em>.
This new interface and endpoint support both raw models (like Gemini 3 Pro) and fully managed agents
(like the Gemini <a href="https://ai.google.dev/gemini-api/docs/deep-research">Deep Research Agent</a>).</p>
<h3 id="the-key-advantages">The Key Advantages</h3>
<p>It simplifies state and context management through <strong>server-side history</strong>, native handling of agent <em>&ldquo;thoughts&rdquo;</em> and data schemas.
It supports <strong>background processing</strong> for long-running tasks (in particular for agents).
Furthermore, interoperability with Agent Development Kit (ADK) and Agent2Agent (A2A) protocol is ongoing.
Finally, it offers advanced capabilities such as tooling (including Google Search and Code execution),
structured JSON outputs, Model Context Protocol (MCP) support, and native multimodal handling.</p>
<h2 id="implementing-the-interactions-api-with-antigravity">Implementing the Interactions API with Antigravity</h2>
<h3 id="a-few-words-about-antigravity">A Few Words About Antigravity</h3>
<p>I decided to put <a href="https://antigravity.google/">Antigravity</a>, Google&rsquo;s new agentic development environment, to the test,
by pointing Antigravity at the Open API 3
<a href="https://ai.google.dev/static/api/interactions.openapi.json">specification of the Interactions API</a>,
and iterating with it to come up with a <strong>Java implementation</strong>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Learning more about Antigravity</span>
      </div>
      <div class="admonition-content">
        <p>You can read more about the key aspects of Antigravity in my colleague, Mete Atamel&rsquo;s article
<a href="https://atamel.dev/posts/2025/12-01_antigravity_editor_tips/">introducing Antigravity and tips and tricks</a>,
as well as his follow-up article on
<a href="https://atamel.dev/posts/2025/11-25_customize_antigravity_rules_workflows/">how to customize Antigravity with rules and workflows</a>.
And my other colleague, Romin Irani, wrote a great <a href="https://codelabs.developers.google.com/getting-started-google-antigravity#3">codelab</a>
to get you started.</p>
      </div>
    </div><p>What I find interesting with Antigravity is that it shifts the developer&rsquo;s perspective.
We&rsquo;re used to having the code editor as our central point of focus and action.
But with Antigravity (advertised as an <em>agentic development platform</em>), the main point of entry is the <strong>agent manager</strong>.</p>
<p>The agent manager has an <strong>inbox</strong>, with the various ongoing implementation tasks.</p>
<p><figure>
  <a href="#img-31e58f9bbf9b63f6ec7b815ffcc216ca">
    <img src="/img/antigravity/inbox.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-31e58f9bbf9b63f6ec7b815ffcc216ca">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/antigravity/inbox.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You have <strong>workspaces</strong> for the different projects you&rsquo;re working on.</p>
<p>There&rsquo;s a <strong>playground</strong> to test ideas, that you can then convert to a proper workspace should the experiment become serious.
This is actually through the playground that I started experimenting with the Interactions API.</p>
<p><figure>
  <a href="#img-a8675a1dbfc9c6bff928b0e483454ebf">
    <img src="/img/antigravity/playground.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a8675a1dbfc9c6bff928b0e483454ebf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/antigravity/playground.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Fear not, you can always switch back to the code editor (a fork of VS Code)!
That&rsquo;s also where you&rsquo;ll be able to approve/reject code changes suggested by Antigravity.</p>
<p><figure>
  <a href="#img-b32a89987137f750b95cdc3470202ad3">
    <img src="/img/antigravity/editor.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b32a89987137f750b95cdc3470202ad3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/antigravity/editor.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Another important aspect of Antigravity is that you start with a prompt explaining the task at hand,
potentially adding all sorts of context (like screenshots or documents).
Then Antigravity is going to create an <strong>implementation plan</strong> that you can comment and review like in a Google Docs.</p>
<p><figure>
  <a href="#img-666f0e57e2d08e917051ce42d69de7f4">
    <img src="/img/antigravity/implementation-plan.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-666f0e57e2d08e917051ce42d69de7f4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/antigravity/implementation-plan.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Once you&rsquo;re happy with the plan, Antigravity will start working for you.
Depending on configuration and task complexity, it might request you to approve changes, tool usage, etc.</p>
<p>Once the task is accomplished, Antigravity will show you a <strong>walkthrough</strong> to guide you through the implementation.
And as always, it&rsquo;s still possible to review it, and ask for further modifications or improvements.</p>
<p><figure>
  <a href="#img-021c4eb8954d2bebeb164b7c7f94ae06">
    <img src="/img/antigravity/walkthrough.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-021c4eb8954d2bebeb164b7c7f94ae06">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/antigravity/walkthrough.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You can also view the <strong>task list</strong> of all the incremental steps Antigravity went through to implement your requests.</p>
<p><figure>
  <a href="#img-6fe857ff5b2d58183ede2c04d018eddd">
    <img src="/img/antigravity/task-list.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6fe857ff5b2d58183ede2c04d018eddd">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/antigravity/task-list.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h3 id="color-me-impressed">Color Me Impressed!</h3>
<p>Honestly, I was quite impressed with Antigravity and Gemini 3 Pro.
It successfully implemented an elegant Java API based on the Interactions API&rsquo;s Open API specification.
Then I iterated with Antigravity to further tweak it to my liking, to add tests, to help me deploy the project to Maven Central.
Not only it was good at coding and following a plan,
but it was very helpful on the command-line for running the build, the deployment commands, etc.</p>
<h2 id="now-lets-interact">Now Let&rsquo;s Interact!</h2>
<p>Antigravity helped me publish my Java implementation of the Interactions API
to <a href="https://central.sonatype.com/artifact/io.github.glaforge/gemini-interactions-api-sdk">Maven Central</a>,
guiding me through the creation of public/private keys and the Maven commands required to prepare and perform the release.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>You can find my <a href="https://github.com/glaforge/gemini-interactions-api-sdk/">implementation on GitHub</a>.
Have a look at the <a href="https://github.com/glaforge/gemini-interactions-api-sdk/blob/main/README.md">README</a> for usage details, but we&rsquo;ll go through them together in this article.</p>
      </div>
    </div><h2 id="setup-and-authentication">Setup and Authentication</h2>
<p>In your Java project&rsquo;s build file, you&rsquo;ll need to specify the dependency to my SDK.</p>
<p>In your <code>pom.xml</code> if you&rsquo;re building with Maven:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.github.glaforge<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>gemini-interactions-api-sdk<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.3.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>And in your <code>build.gradle</code> if you&rsquo;re building with Gradle:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>implementation <span style="color:#4070a0">&#39;io.github.glaforge:gemini-interactions-api-sdk:0.3.0&#39;</span>
</span></span></code></pre></div><p>And you should export a Gemini API key (that you can <a href="https://ai.google.dev/gemini-api/docs/api-key">get in Google AI Studio</a>) as an environment variable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">GEMINI_API_KEY</span><span style="color:#666">=</span>YOUR_API_KEY
</span></span></code></pre></div><p>Now you&rsquo;re ready to interact!</p>
<h2 id="your-first-interaction">Your First Interaction</h2>
<p>Instead of just sending a prompt string, we create an <code>Interaction</code> object.
We specify the kind (the model or agent we want to talk to) and the parameters (our prompt).</p>
<p>Let&rsquo;s make a simple call to Gemini (I&rsquo;ll spare you the <code>import</code>s):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Create a client with your API key</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>GeminiInteractionsClient<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GeminiInteractionsClient.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Create the interaction, choosing a model, and passing the prompt in input</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The output is multimodal, so let&#39;s see if there&#39;s text, image, or thoughts in output</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>response.<span style="color:#4070a0">outputs</span>().<span style="color:#4070a0">forEach</span>((Content<span style="color:#bbb"> </span>output)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">switch</span><span style="color:#bbb"> </span>(output)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>TextContent<span style="color:#bbb"> </span>text<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(text.<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>ImageContent<span style="color:#bbb"> </span>image<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(image.<span style="color:#4070a0">data</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>ThoughtContent<span style="color:#bbb"> </span>thought<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Thought: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>thought.<span style="color:#4070a0">signature</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">default</span><span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Unknown content type: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>output);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is a synchronous call, so you&rsquo;ll be waiting for it to finish generating its answer.</p>
<h2 id="a-multi-turn-conversation">A Multi-turn conversation</h2>
<p>When not taking advantage of the statefulness of the Interactions API, you can still do multi-turn conversations:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Turn(USER,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Hello!&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Turn(MODEL,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Hi! How can I help?&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Turn(USER,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Tell me a joke&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="a-multimodal-request">A Multimodal request</h2>
<p>The Interactions API handles multimodal requests, mixing text, images, audio, videos, etc:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextContent(<span style="color:#4070a0">&#34;Describe this image&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Create an image from Base64 string</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ImageContent(<span style="color:#4070a0">&#34;BASE64_STRING...&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="creating-an-image-with-nano-banana-pro-banana">Creating an Image with Nano Banana Pro &#x1f34c;</h2>
<p>I&rsquo;m a big fan of the Nano Banana model for creating and editing images.
You can easily invoke it as well:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-3-pro-image-preview&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;Create an infographic about blood, organs, and the circulatory system&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseModalities</span>(Modality.<span style="color:#4070a0">IMAGE</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>response.<span style="color:#4070a0">outputs</span>().<span style="color:#4070a0">forEach</span>(content<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(content<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>ImageContent<span style="color:#bbb"> </span>image)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>imageBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Base64.<span style="color:#4070a0">getDecoder</span>().<span style="color:#4070a0">decode</span>(image.<span style="color:#4070a0">data</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Save imageBytes to a file</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="function-calling">Function Calling</h2>
<p>You can pass tools to your model to let it request its use for achieving its goal.
This example is a little bit more involved, of course, as it sets up a method and handles the back and forth exchange:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// 1. Define the tool, its name, description, and input schema as simple Maps</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Function<span style="color:#bbb"> </span>weatherTool<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Function.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;get_weather&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Get the current weather&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">parameters</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;type&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;object&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;properties&#34;</span>,<span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;location&#34;</span>,<span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;type&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;required&#34;</span>,<span style="color:#bbb"> </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;location&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 2. Initial request with tools</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ModelInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;What is the weather in London?&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(weatherTool)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 3. Handle function call</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Content<span style="color:#bbb"> </span>lastOutput<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>interaction.<span style="color:#4070a0">outputs</span>().<span style="color:#4070a0">getLast</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(lastOutput<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">instanceof</span><span style="color:#bbb"> </span>FunctionCallContent<span style="color:#bbb"> </span>call)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#4070a0">&#34;get_weather&#34;</span>.<span style="color:#4070a0">equals</span>(call.<span style="color:#4070a0">name</span>()))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>location<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>call.<span style="color:#4070a0">arguments</span>().<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;location&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Execute local logic...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>weather<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Rainy, 15°C&#34;</span>;<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Simulated result</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 4. Send Function Result</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ModelInteractionParams<span style="color:#bbb"> </span>continuation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// Passing previous interaction ID instead of the whole conversation</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">previousInteractionId</span>(interaction.<span style="color:#4070a0">id</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">input</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>FunctionResultContent(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;function_result&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>call.<span style="color:#4070a0">id</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>call.<span style="color:#4070a0">name</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">false</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;weather&#34;</span>,<span style="color:#bbb"> </span>weather)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Interaction<span style="color:#bbb"> </span>finalResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(continuation);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(finalResponse.<span style="color:#4070a0">outputs</span>().<span style="color:#4070a0">getLast</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>My SDK is fairly bare-bone and doesn&rsquo;t handle automatic function calling like the Gemini SDK does, or LangChain4j, etc.
But it could be a possible enhancement.</p>
<p>In the code, notice how <strong>it handles the session state</strong>.
Instead of passing the whole conversation again when replying with the tool response,
we actually <strong>pass only the interaction ID of the previous call</strong>.
Hence, <strong>state is handled on the server side</strong>!</p>
<h3 id="deep-research">Deep Research</h3>
<p>An important aspect of the Interactions API is its ability to call agents, and not just models.
The <strong>Deep Research agent</strong> is the first to implement the Interactions API.
This is the research agent you may be familiar with from the Gemini web app, used to create long and detailed reports.
This time we&rsquo;re going to create an <em>agent interaction</em> instead of a <em>model interaction</em>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>AgentInteractionParams<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AgentInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">agent</span>(<span style="color:#4070a0">&#34;deep-research-pro-preview-12-2025&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;Research the history of the Google TPUs&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(request);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Poll for completion</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(interaction.<span style="color:#4070a0">status</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span>Status.<span style="color:#4070a0">COMPLETED</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Thread.<span style="color:#4070a0">sleep</span>(1000);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>interaction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">get</span>(interaction.<span style="color:#4070a0">id</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(interaction.<span style="color:#4070a0">outputs</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is an asynchronous task, so you have to wait for its completion by polling.
You can also steer the output report generation by further customizing your prompt, suggesting a particular report structure.</p>
<h3 id="model-context-protocol">Model Context Protocol</h3>
<p>The Interactions API comes with built-in MCP (Model Context Protocol) support.
And by support, I mean that <strong>it handles the MCP call itself</strong>.</p>
<p>A few caveats to be aware of:</p>
<ul>
<li>Currently, it can only call <strong>remote MCP servers</strong> (not local STDIO ones),
and <strong>only Streamable HTTP servers</strong> are supported (Server-Sent Events are deprecated anyway).</li>
<li>Currently, it <strong>works with Gemini 2.5</strong>, but <em>not</em> with Gemini 3 Pro.</li>
<li>MCP server names shouldn&rsquo;t contain a <code>-</code> in their name,
as this character is reserved for namespacing function calls with the name of the server
(i.e. <code>moon_server-current_moon_phase</code> with <code>moon_server</code> being the server name, and <code>current_moon_phase</code> the function name.)
So favor underscores for clearer names.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// 1. Define the MCP Server tool</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>serverName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;moon_server&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>serverUrl<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;https://mn-mcp-server-1029513523185.europe-west1.run.app/mcp&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Tool<span style="color:#bbb"> </span>mcpServer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Tool.<span style="color:#4070a0">McpServer</span>(serverName,<span style="color:#bbb"> </span>serverUrl);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Tool<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>tools<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>List.<span style="color:#4070a0">of</span>(mcpServer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 2. Create Interaction</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ModelInteractionParams<span style="color:#bbb"> </span>createParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ModelInteractionParams.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">input</span>(<span style="color:#4070a0">&#34;What is the current phase of the moon?&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(tools)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 3. Make the Request</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Interaction<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">create</span>(createParams);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 4. Then Analyze the Response...</span><span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="wrapping-up">Wrapping up</h2>
<p>The <strong>Interactions API</strong> unifies the way we handle both fast inference (standard models) and slow thinking (agents).
By generating this SDK, and iterating on it with <strong>Antigravity</strong>,
I&rsquo;ve tried to make it as easy as possible to integrate these new capabilities into Java applications.</p>
<p>Note that this is not (yet?) a production-grade SDK; it&rsquo;s the result of a few hours of experimentation and would benefit from further refinement.
But I was very happy with the outcome, as I was able to quickly experiment with this new API in Java.</p>
<p>Check out the <a href="https://github.com/glaforge/gemini-interactions-api-sdk/blob/main/README.md">README</a> for more configuration details and examples.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>AI Agentic Patterns and Anti-Patterns</title><link>https://glaforge.dev/talks/2025/12/02/ai-agentic-patterns-and-anti-patterns/</link><pubDate>Tue, 02 Dec 2025 12:24:40 +0100</pubDate><guid>https://glaforge.dev/talks/2025/12/02/ai-agentic-patterns-and-anti-patterns/</guid><description>&lt;p>This week, I was on stage at the
&lt;a href="https://events.tech.rocks/e/tech-rocks-summit-2025/fr/session/9b0ac306-ac56-f011-8f7c-6045bd96bdd0/agents-ia-la-nouvelle-frontiere-des-llms">Tech Rocks Summit 2025&lt;/a>
in the beautiful Théâtre de Paris.
This is the first I&amp;rsquo;m attending this event, gathering a nice crowd of CTOs, tech leads, architects, and decision makers.&lt;/p>
&lt;p>My talk focused on what everyone is talking about right now: &lt;strong>AI Agents&lt;/strong>.
And in particular, I was interested in sharing with the audience things I&amp;rsquo;ve seen work or not work in companies, startups,
and via tons of discussions with AI practitioners I met at conferences, meetups, or customer meetings.&lt;/p></description><content:encoded>
<![CDATA[<p>This week, I was on stage at the
<a href="https://events.tech.rocks/e/tech-rocks-summit-2025/fr/session/9b0ac306-ac56-f011-8f7c-6045bd96bdd0/agents-ia-la-nouvelle-frontiere-des-llms">Tech Rocks Summit 2025</a>
in the beautiful Théâtre de Paris.
This is the first I&rsquo;m attending this event, gathering a nice crowd of CTOs, tech leads, architects, and decision makers.</p>
<p>My talk focused on what everyone is talking about right now: <strong>AI Agents</strong>.
And in particular, I was interested in sharing with the audience things I&rsquo;ve seen work or not work in companies, startups,
and via tons of discussions with AI practitioners I met at conferences, meetups, or customer meetings.</p>
<p>Without further ado, here&rsquo;s the deck in French &#x1f1eb;&#x1f1f7; I showed on stage:</p>
<script async class="speakerdeck-embed" data-id="ef0430dc8f2e418b8e0e2e8297cf452a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>And in English as well for my international readers:</p>
<script async class="speakerdeck-embed" data-id="eebc232e3cc34b4b891ba483f5953c3d" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="a-quick-historical-recap">A Quick Historical Recap</h2>
<p>We saw the <a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning)">Transformer</a> wave in 2017, the ChatGPT tsunami in 2023,
and the <a href="https://glaforge.dev/tags/retrieval-augmented-generation/">RAG</a> (Retrieval Augmented Generation) trend in 2024.
In 2025, here we are: Agents are the new frontier for LLMs.</p>
<p>But concretely, what does this change for us, devs and tech leaders?
What works, what doesn&rsquo;t work?
Here are the key points of my presentation.</p>
<h2 id="what-is-an-agent-really">What is an Agent, Really?</h2>
<p>Forget the magic for two minutes. An agent is a fairly simple equation:</p>
<blockquote>
<p>Agent = LLM + Memory + Planning + Tools</p></blockquote>
<p>It is no longer just a model predicting the next word.
It is a system that observes, plans, acts, and thinks (the famous Reflection loop to correct its own errors).</p>
<h2 id="architecture-patterns-that-work">Architecture Patterns that Work</h2>
<p>I presented 4 patterns to avoid reinventing the wheel:</p>
<ul>
<li><strong>The Orchestrator</strong>: A supervisor agent that delegates to specialized <em>sub-agents</em>.
This is crucial for breaking down a complex task into digestible chunks.</li>
<li><strong>Rethinking Tools</strong>: Don&rsquo;t just throw your raw REST API at the LLM.
Create <em>&ldquo;business task&rdquo;</em> oriented tools (e.g., &ldquo;Schedule Meeting&rdquo; vs <code>POST /calendar/v1/events</code>).
Fewer tools = less confusion = more determinism.</li>
<li><strong>MCP (Model Context Protocol)</strong>: This is the future standard, essentially the <em>USB for AI tools</em>.
It standardizes how an agent connects to its tools, launched by Anthropic and now widely adopted (but still rapidly evolving).</li>
<li><strong>A2A (Agent to Agent)</strong>: Google and its partners are pushing this extensible protocol
so that agents can discover and collaborate with each other, regardless of their language or framework.</li>
</ul>
<h2 id="traps-to-avoid-anti-patterns">Traps to Avoid (Anti-Patterns)</h2>
<p>I insisted on this because I see teams falling into these traps:</p>
<ul>
<li><strong>The &ldquo;Chatbot Mandate&rdquo;</strong>: Does your leadership want <em>&ldquo;a chatbot&rdquo;</em>? Resist.
AI should often be invisible (like a Head-Up Display), not necessarily an endless conversation.</li>
<li><strong>Insufficient Vibe-Checking</strong>: <em>&ldquo;It looks like it works&rdquo;</em> is not a testing strategy.
You need <em>Golden Responses</em>, LLM-as-a-Judge, and a <strong>real evaluation phase</strong>.</li>
<li><strong>Silent Confabulation</strong>: RAG is great, but if the AI invents things, it&rsquo;s dangerous.
Force source citation and aim for IVO
(<em>Immediately Validatable Output</em>, coined by my colleague <a href="https://www.zackakil.com/">Zack Akil</a>):
the user must be able to verify the result at a glance.</li>
<li><strong>The Coding &ldquo;Rabbit Hole&rdquo;</strong>: Coding agents are stunning but can lead you down the wrong path with incredible confidence.
(<em>&ldquo;You&rsquo;re absolutely right!&rdquo;</em>)
Keep a cool head and focus on value (MVP), not feature creep.</li>
</ul>
<h2 id="back-at-the-office-what-do-we-do">Back at the Office: What Do We Do?</h2>
<p>I concluded with a <em>&ldquo;Todo List&rdquo;</em> for when attendes are back at the office:</p>
<ul>
<li>Don&rsquo;t ask yourself <em>&ldquo;Where can I squeeze in a chatbot?&rdquo;</em>.
Instead, identify the most painful business process (the Critical User Journey).</li>
<li>Experiment small. The goal is to learn.</li>
<li>Measure &amp; Evaluate. It&rsquo;s your users who will tell you if you&rsquo;re right, not the hype.</li>
</ul>
<p>The agent might not buy happiness, but implemented well, it can seriously contribute to it! &#x1f604;</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Gemini Is Cooking Bananas Under Antigravity</title><link>https://glaforge.dev/posts/2025/11/21/gemini-is-cooking-bananas-under-antigravity/</link><pubDate>Fri, 21 Nov 2025 11:14:08 +0100</pubDate><guid>https://glaforge.dev/posts/2025/11/21/gemini-is-cooking-bananas-under-antigravity/</guid><description>&lt;p>What a wild title, isn&amp;rsquo;t it?
It&amp;rsquo;s a catchy one, not generated by AI, to illustrate this crazy week of announcements by Google.
Of course, there are big highlights like &lt;strong>Gemini 3 Pro&lt;/strong>, &lt;strong>Antigravity&lt;/strong>, or &lt;strong>Nano Banana Pro&lt;/strong>, but not only,
and this is the purpose of the article to share with you everything,
including links to all the interesting materials about those news.&lt;/p>
&lt;h2 id="gemini-3-pro">Gemini 3 Pro&lt;/h2>
&lt;p>The community was eagerly anticipating the release of Gemini 3.
Gemini 3 Pro is a state-of-the-art model, with excellent multimodal capabilities,
advanced reasoning, excellent at coding, and other agentic activities.&lt;/p></description><content:encoded>
<![CDATA[<p>What a wild title, isn&rsquo;t it?
It&rsquo;s a catchy one, not generated by AI, to illustrate this crazy week of announcements by Google.
Of course, there are big highlights like <strong>Gemini 3 Pro</strong>, <strong>Antigravity</strong>, or <strong>Nano Banana Pro</strong>, but not only,
and this is the purpose of the article to share with you everything,
including links to all the interesting materials about those news.</p>
<h2 id="gemini-3-pro">Gemini 3 Pro</h2>
<p>The community was eagerly anticipating the release of Gemini 3.
Gemini 3 Pro is a state-of-the-art model, with excellent multimodal capabilities,
advanced reasoning, excellent at coding, and other agentic activities.</p>
<p>You&rsquo;ll see below the results on various benchmarks, which are quite impressive,
and represents a significant leap forward on some of them:
<figure>
  <a href="#img-def7ccaffcd015293ec20ee551ea0d9c">
    <img src="https://storage.googleapis.com/gweb-uniblog-publish-prod/documents/gemini_3_table_final_HLE_Tools_on.gif"
      alt="Gemini 3 Pro results on benchmarks"
       />
  </a>
  <figcaption>Gemini 3 Pro results on benchmarks</figcaption>
</figure>
<div class="lightbox" id="img-def7ccaffcd015293ec20ee551ea0d9c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/gweb-uniblog-publish-prod/documents/gemini_3_table_final_HLE_Tools_on.gif"
    alt="Gemini 3 Pro results on benchmarks"
     />
  <div class="lightbox-caption">Gemini 3 Pro results on benchmarks</div>
</div>
</p>
<p>Here are a few pointers with more details:</p>
<ul>
<li><a href="https://blog.google/products/gemini/gemini-3/">A new era of intelligence with Gemini 3</a> with lots of illustrations of what Gemini 3 can do,
and also the mention of Gemini 3 Deep Think, which ranks super high on ARC-AGI-2 or Humanity&rsquo;s Last Exam benchmarks.</li>
<li><a href="https://blog.google/technology/developers/gemini-3-developers/">Start building with Gemini 3</a> mentioning agentic coding, Antigravity (we&rsquo;ll come back to it),
vibe coding, visual, video and spatial reasoning.</li>
<li><a href="https://blog.google/products/gemini/gemini-3-gemini-app/">Gemini 3 brings upgraded smarts and new capabilities to the Gemini app</a>
which discusses what Gemini 3 brings inside the Gemini app with live <em>generative interfaces</em> (generating live UIs on the fly!)</li>
<li><a href="https://blog.google/products/gemini/gemini-3-collection/">All the articles about Gemini 3</a>
which lists all the <em>official</em> blog posts about the launch of Gemini 3, if you want to dive deeper.</li>
</ul>
<h3 id="available-in-many-places">Available In Many Places</h3>
<p>Gemini 3 Pro is available in other places:</p>
<ul>
<li>in <a href="https://developers.googleblog.com/jules-gemini-3/">Jules</a> the asynchronous coding agent,</li>
<li>in <a href="https://firebase.blog/posts/2025/11/gemini-3-firebase-ai-logic">Firebase AI Logic</a>,</li>
<li>in <a href="https://android-developers.googleblog.com/2025/11/gemini-3-is-now-available-for-ai.html">Android Studio</a>,</li>
<li>in <a href="https://blog.jetbrains.com/ai/2025/11/gemini-3-pro-is-now-available-in-jetbrains-ides/">JetBrains IDEs</a> in Junie and the AI chat,</li>
<li>and <a href="https://github.blog/changelog/2025-11-18-gemini-3-pro-is-in-public-preview-for-github-copilot/">inside GitHub Copilot</a>.</li>
</ul>
<h3 id="available-in-gemini-cli">Available In Gemini CLI</h3>
<p>Gemini CLI has also been updated to take advantage of Gemini 3 Pro.</p>
<p>Be sure to read this <a href="https://geminicli.com/docs/get-started/gemini-3/">document</a> which explains how to access Gemini 3 Pro in Gemini CLI,
as it&rsquo;s available to Google AI Ultra users, and paid Gemini and Vertex API key holders,
so if you&rsquo;re not in these categories, you might want to wish the <a href="https://goo.gle/geminicli-waitlist-signup">waitlist</a>
to experience all the fun!</p>
<p>Check out this article as well:</p>
<ul>
<li><a href="https://developers.googleblog.com/en/5-things-to-try-with-gemini-3-pro-in-gemini-cli/">5 things to try with Gemini 3 Pro in Gemini CLI</a>
on how to setup Gemini 3, how to turn visual ideas into working apps, how to generate shell commands from natural language,
or accurate documentation from your codebase, and how it can help you debug issues.</li>
</ul>
<h2 id="antigravity">Antigravity</h2>
<p>Now let&rsquo;s move on to <a href="https://antigravity.google/">Antigravity</a>, a new agentic development platform, based on VS Code.
Of course, you have all the usual functionalities of a text editor, with (smart) code completion, and all.
However the interesting aspect of Antigravity is that the main window is actually not the IDE,
but the central place where things happen is the <em>agent manager</em>, where you&rsquo;ll launch your requests.
They will be interpreted and will trigger the creation of a plan, with various steps.
You&rsquo;ll be able to comment and review the plan, for further adjustments.
Then Gemini 3 will handle the implementation of those tasks.
And Antigravity will produce various other artifacts along the way, like the task lists, the walkthroughs, screenshots and even browser recordings.</p>
<p><figure>
  <a href="#img-e47a699e68d0d2d300827bb8ac2765da">
    <img src="https://antigravity.google/assets/image/product/manager.png"
      alt="Antigravity agent manager screenshot"
       />
  </a>
  <figcaption>Antigravity agent manager screenshot</figcaption>
</figure>
<div class="lightbox" id="img-e47a699e68d0d2d300827bb8ac2765da">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://antigravity.google/assets/image/product/manager.png"
    alt="Antigravity agent manager screenshot"
     />
  <div class="lightbox-caption">Antigravity agent manager screenshot</div>
</div>
</p>
<p>It&rsquo;s also possible to use Claude Sonnet and GPT-OSS, so this product is not limited to just Gemini 3, however good it may be.</p>
<p>What I find impressive is the nice integration with the browser, to inspect and see how the implementation looks like,
and further loop back to continue improving it or fixing it if it&rsquo;s not what the user asked.</p>
<p>I haven&rsquo;t covered Nano Banana Pro yet, but with that image generation and editing model integrated in Antigravity,
you&rsquo;re able to create designs, update them visually with manual squiggles and such, and have Antigravity implement that design for you!</p>
<p>Articles to dive more:</p>
<ul>
<li><a href="https://antigravity.google/blog/introducing-google-antigravity">Introducing Google Antigravity, a New Era in AI-Assisted Software Development</a></li>
<li><a href="https://developers.googleblog.com/build-with-google-antigravity-our-new-agentic-development-platform/">Build with Google Antigravity, our new agentic development platform</a>,
the blog post announcing Antigravity</li>
<li><a href="https://antigravity.google/docs/get-started">Getting Started</a> to know how to get your environment ready and configured</li>
<li><a href="https://antigravity.google/blog/nano-banana-pro-in-google-antigravity">Nano Banana Pro in Google Antigravity</a>
dives into the integration with Nano Banana to generate UI mockups, incrementally, and get your design implemented.</li>
<li><a href="https://www.youtube.com/@googleantigravity">Antigravity YouTube Channel</a> with plenty of small videos, including a longer one to
<a href="https://www.youtube.com/watch?v=nTOVIGsqCuY">learn the basics</a>, and another short one demonstrating the
<a href="https://www.youtube.com/watch?v=FB6HO7CZHWw">Nano Banana integration</a>.</li>
<li><a href="https://medium.com/google-cloud/tutorial-getting-started-with-google-antigravity-b5cc74c103c2">Tutorial : Getting Started with Google Antigravity</a>
by my awesome colleague Romin Irani who wrote a very detailed step-by-step tutorial to get started easily,
and who also created a <a href="https://codelabs.developers.google.com/getting-started-google-antigravity?hl=en#0">codelab</a>
with precise instructions to go through.</li>
</ul>
<h2 id="nano-banana-pro">Nano Banana Pro</h2>
<p>I wrote about <strong>Nano Banana</strong> in previous articles showing how to
<a href="https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/">call it from a Javelit frontend</a>,
how to create <a href="https://glaforge.dev/posts/2025/09/22/creative-ai-agents-with-adk-and-nano-banana/">ADK agents with Nano Banana</a>,
and simply how to <a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">call Nano Banana from Java</a>.</p>
<p>I was already super impressed with its capabilities in terms of image edition.
However if I wanted the best quality, I would usually start with an Imagen generation,
then I&rsquo;d iterate with Nano Banana for editing.
But now, Nano Banana Pro is another level above both Imagen 4 Ultra and the original Nano Banana,
in terms of prompt adherence, understanding of user intent, creativity, and quality of generation.</p>
<p>When you use it, you&rsquo;ll notice how great it is at text, even lots of text!
It made huge leaps in terms of typography.
And what&rsquo;s crazy, with the fact it&rsquo;s based on Gemini 3 Pro, is that it&rsquo;s able to understand articles or videos,
and generate detailed and precise infographics about them!
It&rsquo;s connected to Google Search, and it can research, for example, the weather in Paris, and create a diagram with live data!
I&rsquo;ll certainly come back to that topic in forthcoming articles.</p>
<p>For example, here&rsquo;s some infographics that Nano Banana Pro created to summarize this article:
<figure>
  <a href="#img-98ad0e4d4293c4a1134aae9db1e9fe66">
    <img src="/img/nano-banana/nb2-article-infographics.jpg"
      alt="Illustration of this article via Nano Banana Pro"
       />
  </a>
  <figcaption>Illustration of this article via Nano Banana Pro</figcaption>
</figure>
<div class="lightbox" id="img-98ad0e4d4293c4a1134aae9db1e9fe66">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/nb2-article-infographics.jpg"
    alt="Illustration of this article via Nano Banana Pro"
     />
  <div class="lightbox-caption">Illustration of this article via Nano Banana Pro</div>
</div>
</p>
<p>You can mix <em>ingredients</em> together like different characters (with character consistency),
use them for some kind of <em>transfer learning</em> to follow a certain style.</p>
<p>It has a high level of understanding of scenes and can easily change the lighting, the angle view.</p>
<p>You can generate images up to 4K! And have a wide range of aspect ratios to choose from.</p>
<p>Pay attention to the pricing, however, as it&rsquo;s more expensive than Nano Banana.
So for small edits, maybe you&rsquo;ll stick with Nano Banana, but when you want the most complex design and quality, choose Nano Banana Pro.</p>
<p>Some links to dive deeper:</p>
<ul>
<li><a href="https://blog.google/technology/ai/nano-banana-pro/">Introducing Nano Banana Pro</a>, the blog post.</li>
<li><a href="https://blog.google/technology/developers/gemini-3-pro-image-developers/">Build with Nano Banana Pro, our Gemini 3 Pro Image model</a>,
another official blog post on the launch.</li>
<li><a href="https://deepmind.google/models/gemini-image/pro/">https://deepmind.google/models/gemini-image/pro/</a>,
DeepMind&rsquo;s product page showing some impressive examples of usage of the model.</li>
<li><a href="https://storage.googleapis.com/deepmind-media/Model-Cards/Gemini-3-Pro-Image-Model-Card.pdf">Gemini 3 Pro Image Model Card</a>
for all the technical details of the model.</li>
<li><a href="https://medium.com/google-cloud/testing-gemini-3-pro-image-f585236ae411">Testing Gemini 3 Pro Image Model</a>
written by my awesome colleague Laurent Picard who demonstrates character consistency and the high quality of generated images.</li>
<li><a href="https://blog.google/products/gemini/prompting-tips-nano-banana-pro/">7 tips to get the most out of Nano Banana Pro</a>
to get the best out of the model.</li>
</ul>
<h2 id="towards-live-and-richer-conversational-uis">Towards Live And Richer Conversational UIs</h2>
<p>I wanted to finish this overview of the announcements of the week with something that you might not have heard of,
but which I think is interesting for the future of generative AI and conversational interfaces.</p>
<p>In my talks, at meetups, in conversations with developers, I often explain that imposing chatbots everywhere
is not the best use and ideal integration of AI in their applications,
and that more transparent and seamless generative AI integrations are preferred for ensuring their success with users and customers.</p>
<p>I think the following two projects are helping towards a smoother integration of generative AI:</p>
<p><figure>
  <a href="#img-3f63f8b2167b73bba05691587bee30e4">
    <img src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*yZgCoXy4aivZfEjgTJMOrA.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3f63f8b2167b73bba05691587bee30e4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*yZgCoXy4aivZfEjgTJMOrA.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<ul>
<li><a href="https://blog.flutter.dev/rich-and-dynamic-user-interfaces-with-flutter-and-generative-ui-178405af2455">Rich and dynamic user interfaces with Flutter and generative UI</a></li>
</ul>
<p>The GenUI SDK for <a href="https://flutter.dev/">Flutter</a> allows developers to create dynamic, personalized user interfaces using LLMs,
transforming text-based conversations into rich, interactive experiences.
It acts as an orchestration layer, sending user prompts and available widgets to an AI agent
which generates content and a suitable UI description.
The SDK then deserializes and dynamically renders this UI into interactive Flutter widgets,
with user interactions triggering subsequent updates.
This system relies on the (upcoming) <a href="https://a2ui.org/">A2UI protocol</a>.</p>
<ul>
<li><a href="https://research.google/blog/generative-ui-a-rich-custom-visual-interactive-user-experience-for-any-prompt/">Generative UI: A rich, custom, visual interactive user experience for any prompt</a></li>
</ul>
<p>Generative UI enables AI models to create custom, interactive user experiences, like web pages and tools, directly from any prompt.
This dynamic capability is rolling out in the Gemini app and Google Search&rsquo;s AI Mode,
leveraging Gemini 3 Pro with tool access and detailed instructions.
Users prefer these outputs over standard AI text, though human-expert designs remain slightly favored.
Despite current challenges with speed and occasional inaccuracies, Generative UI signifies a major step toward fully AI-generated and adaptive interfaces.</p>
<h2 id="now-your-turn-to-have-fun">Now, Your Turn To Have Fun!</h2>
<p>With all those announcements, and key pointers to learn more about them,
I hope you&rsquo;re ready to build exciting new things with Gemini 3 Pro, Antigravity, Nano Banana Pro, and more!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Semantic Document Similarity: Finding related articles with vector embedding models</title><link>https://glaforge.dev/posts/2025/11/12/finding-related-articles-with-vector-embedding-models/</link><pubDate>Wed, 12 Nov 2025 08:52:42 +0100</pubDate><guid>https://glaforge.dev/posts/2025/11/12/finding-related-articles-with-vector-embedding-models/</guid><description>&lt;p>When you enjoyed reading an article on a blog, you might be interested in other, similar articles.
As a blog author, you want to surface that relevant content to your readers to keep them engaged.
For a long time, I&amp;rsquo;ve wanted to add a &lt;em>&amp;ldquo;Similar articles&amp;rdquo;&lt;/em> section to my posts, but I never quite found a simple and effective way to do it.
Hugo (the static stite generator I&amp;rsquo;m using) has a &lt;a href="https://gohugo.io/content-management/related-content/">related content&lt;/a> concept, but it wasn&amp;rsquo;t really what I was after.&lt;/p></description><content:encoded>
<![CDATA[<p>When you enjoyed reading an article on a blog, you might be interested in other, similar articles.
As a blog author, you want to surface that relevant content to your readers to keep them engaged.
For a long time, I&rsquo;ve wanted to add a <em>&ldquo;Similar articles&rdquo;</em> section to my posts, but I never quite found a simple and effective way to do it.
Hugo (the static stite generator I&rsquo;m using) has a <a href="https://gohugo.io/content-management/related-content/">related content</a> concept, but it wasn&rsquo;t really what I was after.</p>
<p>But with the power of modern generative AI models (in particular, embedding models),
I&rsquo;ve finally implemented a system that automatically finds and displays related content.
In this post, I&rsquo;ll walk you through how I did it.</p>
<h2 id="the-core-idea-from-words-to-numbers">The Core Idea: From Words to Numbers</h2>
<p>The fundamental challenge is to determine how <em>&ldquo;similar&rdquo;</em> two articles are.
Humans can do this intuitively by reading them, but how can a computer do it? The answer lies in a technique called <strong>vector embeddings</strong>.</p>
<p>The idea is to convert a piece of text into a list of numbers, called a vector.
This vector represents the text&rsquo;s semantic meaning.
Texts with similar meanings will have vectors that are &ldquo;close&rdquo; to each other in a multi-dimensional space.
So, the process looks like this:</p>
<ol>
<li><strong>Summarize:</strong> For each article, create a concise summary that captures its essence.</li>
<li><strong>Embed:</strong> Convert each summary into a vector embedding.</li>
<li><strong>Compare:</strong> Calculate the &ldquo;distance&rdquo; between every pair of vectors.</li>
<li><strong>Display:</strong> For each article, find the ones with the closest vectors and display them.</li>
</ol>
<p>Let&rsquo;s dive into each step.</p>
<h3 id="step-1-summarizing-the-content">Step 1: Summarizing the Content</h3>
<p>My blog posts can be quite long and cover various topics.
To get a clean signal for comparison, I first decided to summarize each article.
This helps to distill the core message and remove noise.</p>
<p>This approach was also necessary because most embedding models have limitations on the size of the input text they can accept.
Many of my articles were too long for the embedding model&rsquo;s input, so creating detailed summaries that are as close as possible to the original content ensures that the resulting embedding vector accurately represents the article&rsquo;s meaning.</p>
<p>For this task, I turned to Google&rsquo;s <strong>Gemini</strong> model, specifically <code>gemini-2.5-flash</code>, which is fast and effective.
With the help of <a href="https://geminicli.com/">Gemini CLI</a>,
I wrote a simple Node.js <a href="https://github.com/glaforge/glaforge.github.io/blob/main/summarize-and-embed.js">script</a>
that iterates through all my Markdown files, extracts the content, and sends it to the Gemini API with a straightforward prompt:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> prompt <span style="color:#666">=</span> <span style="color:#4070a0">`Please provide a long, detailed, and factual summary
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">of the following article. The summary should capture the main points,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">key arguments, and any important conclusions. It should be
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">comprehensive enough to give a good understanding of the article&#39;s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">content.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">Article:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0"></span><span style="color:#70a0d0">${</span>text<span style="color:#70a0d0">}</span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">Summary:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">`</span>
</span></span></code></pre></div><p>To call Gemini, you need to import the <code>@google/genai module</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> {GoogleGenAI} from <span style="color:#4070a0">&#39;@google/genai&#39;</span>;
</span></span></code></pre></div><p>Pass an API key (that you can obtain from <a href="https://aistudio.google.com/api-keys">Google AI Studio</a>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> genAI <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> GoogleGenAI({apiKey<span style="color:#666">:</span> API_KEY});
</span></span></code></pre></div><p>Then call the model to create the summary:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> result <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> genAI.models.generateContent({
</span></span><span style="display:flex;"><span>    model<span style="color:#666">:</span> <span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>,
</span></span><span style="display:flex;"><span>    contents<span style="color:#666">:</span> prompt,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> summary <span style="color:#666">=</span> result<span style="color:#666">?</span>.candidates<span style="color:#666">?</span>.[<span style="color:#40a070">0</span>]<span style="color:#666">?</span>.content<span style="color:#666">?</span>.parts<span style="color:#666">?</span>.[<span style="color:#40a070">0</span>]<span style="color:#666">?</span>.text;
</span></span></code></pre></div><p>To avoid re-generating summaries every time I run the script, I added a simple caching mechanism.
The first time the script runs, it generates a summary and saves it to a text file in a <code>summaries/</code> directory, mirroring the structure of my <code>content/posts/</code> directory. On subsequent runs, it just reads the summary from the cache.</p>
<h3 id="step-2-creating-vector-embeddings">Step 2: Creating Vector Embeddings</h3>
<p>Once I have a summary, the next step is to convert it into a vector. This is where embedding models come in.
I used another of Google&rsquo;s models, <code>gemini-embedding-001</code>, which is designed for this exact purpose.</p>
<p>The model takes a piece of text and returns a vector.
You can even configure the size of this vector.
I chose a dimensionality of 256, which provides a good balance between detail and performance (to speed up vector similarity calculations).</p>
<p>I also specified the <code>task_type</code> as <code>SEMANTIC_SIMILARITY</code>, which optimizes the embeddings for this kind of comparison task.
You can check the other <a href="https://ai.google.dev/gemini-api/docs/embeddings#supported-task-types">task types</a>, for classification, clustering, Q&amp;A, etc.</p>
<p>Again, this is fairly short to write in JavaScript:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> result <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> genAI.models.embedContent({
</span></span><span style="display:flex;"><span>    model<span style="color:#666">:</span> <span style="color:#4070a0">&#34;gemini-embedding-001&#34;</span>,
</span></span><span style="display:flex;"><span>    contents<span style="color:#666">:</span> texts,
</span></span><span style="display:flex;"><span>    task_type<span style="color:#666">:</span> <span style="color:#4070a0">&#34;SEMANTIC_SIMILARITY&#34;</span>,
</span></span><span style="display:flex;"><span>    output_dimensionality<span style="color:#666">:</span> <span style="color:#40a070">256</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> embeddings <span style="color:#666">=</span> result<span style="color:#666">?</span>.embeddings;
</span></span></code></pre></div><p>My script collects all the summaries and sends them to the embedding model in batches of 100 (the maximum allowed by the API).
This is much more efficient than sending them one by one.</p>
<h3 id="step-3-calculating-similarity">Step 3: Calculating Similarity</h3>
<p>Now that every article&rsquo;s summary is represented by a 256-dimensional vector, how do we measure the &ldquo;distance&rdquo; between them?
The most common method for this is <strong>cosine similarity</strong>.</p>
<p>Imagine each vector as an arrow pointing in a certain direction in a 256-dimensional space.
The <a href="https://en.wikipedia.org/wiki/Cosine_similarity">cosine similarity</a> measures the angle between two of these arrows.</p>
<ul>
<li>If the arrows point in the same direction (a small angle), the texts are very similar, and the cosine similarity is close to 1.</li>
<li>If the arrows are perpendicular, the texts are unrelated, and the similarity is 0.</li>
<li>If they point in opposite directions, they are dissimilar, and the similarity is -1.</li>
</ul>
<p>The calculation itself is quite simple.
When the vectors are normalized
(which I do, as the vectors are not always normalized depending on the output dimensionality you&rsquo;ve chosen),
the cosine similarity is just their <a href="https://en.wikipedia.org/wiki/Dot_product">dot product</a>.</p>
<h3 id="step-4-putting-it-all-together-in-a-script">Step 4: Putting It All Together in a Script</h3>
<p>The Node.js script that orchestrates this whole process is available on GitHub: <a href="https://github.com/glaforge/glaforge.github.io/blob/main/summarize-and-embed.js">summarize-and-embed.js</a>.</p>
<p>Here&rsquo;s how it works:</p>
<ol>
<li>It finds all my blog posts.</li>
<li>It loops through them, either loading the summary from the cache or generating a new one (with a one-second delay between API calls to avoid rate limiting).</li>
<li>It generates embeddings for all summaries in batches.</li>
<li>It then iterates through every article and calculates the cosine similarity with every other article.</li>
<li>For each article, it sorts the others by their similarity score in descending order. I also filter out any articles with a similarity score below 0.75 to ensure the recommendations are high-quality.</li>
<li>Finally, it takes the top 3 most similar articles and updates the frontmatter of the original Markdown file, adding a <code>similar</code> array with the paths to the related posts.</li>
</ol>
<h3 id="step-5-displaying-the-similar-articles-in-hugo">Step 5: Displaying the Similar Articles in Hugo</h3>
<p>With the <code>similar</code> array in my frontmatter, the final step was to display the links in my blog&rsquo;s theme.
I&rsquo;m using the Hugo static site generator, and this was surprisingly easy.
I edited the partial template responsible for rendering a single post to include this snippet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{{ with .Params.similar }}
</span></span><span style="display:flex;"><span>&lt;<span style="color:#062873;font-weight:bold">div</span> <span style="color:#4070a0">class</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;similar-articles&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#062873;font-weight:bold">h3</span>&gt;Similar articles&lt;/<span style="color:#062873;font-weight:bold">h3</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#062873;font-weight:bold">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {{ range . }}
</span></span><span style="display:flex;"><span>        {{ with site.GetPage . }}
</span></span><span style="display:flex;"><span>            &lt;<span style="color:#062873;font-weight:bold">li</span>&gt;&lt;<span style="color:#062873;font-weight:bold">a</span> <span style="color:#4070a0">href</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;{{ .RelPermalink }}&#34;</span>&gt;{{ .Title }}&lt;/<span style="color:#062873;font-weight:bold">a</span>&gt;&lt;/<span style="color:#062873;font-weight:bold">li</span>&gt;
</span></span><span style="display:flex;"><span>        {{ end }}
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#062873;font-weight:bold">ul</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#062873;font-weight:bold">div</span>&gt;
</span></span><span style="display:flex;"><span>{{ end }}
</span></span></code></pre></div><p>This code checks if the <code>similar</code> parameter exists. If it does, it loops through the paths.
For each path, it uses Hugo&rsquo;s <code>site.GetPage</code> function to fetch the full page object, from which I can get the <code>.Title</code> and <code>.RelPermalink</code>.
And with a little bit of CSS, it looks like a nice, integrated part of my blog.</p>
<h2 id="what-does-it-look-like">What Does It Look Like?</h2>
<p>Let&rsquo;s have a look at a few examples (you&rsquo;ll see the recommended articles at the bottom of each post).</p>
<p>For my article on the <a href="https://glaforge.dev/posts/2025/11/03/driving-a-web-browser-with-gemini-computer-use-model-in-java/">Gemini Computer Use model</a>,
the vector similarity suggested 3 articles related to Gemini as well:</p>
<ul>
<li><a href="https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/">Get Started with Gemini in Java</a></li>
<li><a href="https://glaforge.dev/posts/2024/09/05/new-gemini-model-in-langchain4j/">New Gemini model in LangChain4j</a></li>
<li><a href="https://glaforge.dev/posts/2023/12/22/gemini-function-calling/">Gemini Function Calling</a></li>
</ul>
<p>On the article about <a href="https://glaforge.dev/posts/2025/09/22/creative-ai-agents-with-adk-and-nano-banana/">using the Nano Banana image model within an ADK agent</a>,
the algorithm refered to those 3 articles on getting started with ADK, how to call Nano Banana from Java, or how to get started with Gemini:</p>
<ul>
<li><a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">Generating and editing images with Nano Banana in Java</a></li>
<li><a href="https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/">Write AI agents in Java — Agent Development Kit getting started guide</a></li>
<li><a href="https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/">Get Started with Gemini in Java</a></li>
</ul>
<p>For some quite unique articles, sometimes it would return even just one or zero result at all,
but that&rsquo;s expected, because if there are no articles that are similar,
I don&rsquo;t want my readers to get suggestions about totally unrelated material.</p>
<h2 id="considerations-and-other-approaches">Considerations and Other Approaches</h2>
<p>I&rsquo;m happy with the <em>summarize &ndash;&gt; embed approach</em> as it is simple and gives me pretty good recommendations.
However, I considered some alternatives.</p>
<h3 id="leveraging-tags">Leveraging Tags</h3>
<p>First of all, I&rsquo;m using <em>tags</em> on my blog.
So I considered leveraging them, as I strive for consistency in applying them to my articles.
For instance, I could have narrowed the search space to only articles sharing at least one common tag.
Alternatively, a heuristic could have been devised to influence the ranking, favoring articles with a higher number of shared tags.</p>
<h3 id="averaging-vectors-mean-pooling">Averaging Vectors (Mean Pooling)</h3>
<p>I went with the summarization route because of the limited input size of the embedding model.
But <strong>summarization is a lossy process</strong>, as you lose some precision along the way.
Through some experiments, I noticed that summarization could sometimes result in a similarity score that was 0.1 lower than a direct embedding of the full text.
I have a threshold of similarity of &gt;0.75, so 0.1 can make a difference for article selection.</p>
<p>If you chunk your article into smaller passages (according to the maximum number of characters or tokens your embedding model can ingest), you&rsquo;ll end up with multiple chunks and their respective vector embeddings.
One approach is to calculate the average of these vectors, assuming this &lsquo;mean pooled&rsquo; vector isn&rsquo;t too far from the embedding of the entire text.
I didn&rsquo;t extensively explore this, but my initial impressions suggested it sometimes performed worse than the summarization and embedding method.
This wasn&rsquo;t a scientific study, just my gut feeling, and the finer details of research papers on mean pooling escaped me at the time! Averaging vectors is often mentioned in articles as <em>mean pooling</em>.</p>
<h3 id="passage-to-passage-comparisons">Passage to Passage Comparisons</h3>
<p>A higher fidelity approach would have been to do a passage-to-passage comparison.
If you&rsquo;re familiar with <strong>RAG</strong> (<a href="https://glaforge.dev/tags/retrieval-augmented-generation/">Retrieval Augmented Generation</a>), you know about the chunking phase we&rsquo;ve just mentioned.
In RAG, you compare the query&rsquo;s vector with vectors of document passages.
This passage-to-passage comparison approach could also be applied to compare a full article with other full articles.
You could then devise a function to aggregate these passage-level similarities into an overall document score, favoring documents with more highly similar passages.
This could be a higher fidelity comparison, but I didn&rsquo;t explore this idea as the number of embedding requests and matrix comparisons is significantly higher (and thus more expensive and time consuming).</p>
<h3 id="a-mix-with-reciprocal-rank-fusion">A Mix! With Reciprocal Rank Fusion</h3>
<p>Last but not least, you could even combine those approaches together: summarize/embed, tag set comparison, passage-to-passage chunking/embedding and comparison.
Each approach yields a ranking of similar articles, but their scores aren&rsquo;t necessarily on the same scale, as they&rsquo;re comparing different aspects of similarity.
In such situations, you can use methods like <a href="https://medium.com/@devalshah1619/mathematical-intuition-behind-reciprocal-rank-fusion-rrf-explained-in-2-mins-002df0cc5e2a">Reciprocal Rank Fusion</a> (RRF)
to combine different rankings together.
RRF is often used in hybrid search scenarios, where you want to combine different searches together, like a classical keyword-based search, and a semantic search.</p>
<p>Ultimately, since the simple summarization-and-embed solution provided good results, I decided against further complicating the system.
Sometimes, being pragmatic and choosing a &lsquo;good enough&rsquo; solution is more effective than pursuing the absolute best.
However, I wanted to share this thought process and highlight that various other solutions exist.</p>
<h2 id="conclusion">Conclusion</h2>
<p>And there you have it!
It might seem like a lot of steps, but the overall process is quite logical.
By leveraging the power of <strong>summarization</strong> and <strong>embedding models</strong>, I was able to build a powerful <strong>related articles feature</strong>.
For now, I&rsquo;m still running the script manually, but later on, I&rsquo;ll see how I can integrate it in my GitHub Actions workflow.</p>
<p>It&rsquo;s a great example of how generative AI can be used to enhance existing applications in practical and useful ways.
I&rsquo;m really happy with the results, and I hope my readers will find the new recommendations useful for discovering more content they&rsquo;re interested in.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Driving a web browser with Gemini's Computer Use model in Java</title><link>https://glaforge.dev/posts/2025/11/03/driving-a-web-browser-with-gemini-computer-use-model-in-java/</link><pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate><guid>https://glaforge.dev/posts/2025/11/03/driving-a-web-browser-with-gemini-computer-use-model-in-java/</guid><description>&lt;p>In this article, I&amp;rsquo;ll guide you through the process of programmatically interacting with a web browser
using the new &lt;a href="https://ai.google.dev/gemini-api/docs/computer-use">Computer Use model&lt;/a> in Gemini 2.5 Pro.
We&amp;rsquo;ll accomplish this in Java &amp;#x2615; leveraging Microsoft&amp;rsquo;s powerful &lt;a href="https://playwright.dev/java/">Playwright Java SDK&lt;/a> to handle the browser automation.&lt;/p>
&lt;h2 id="the-new-computer-use-model">The New Computer Use Model&lt;/h2>
&lt;p>Unveiled in this &lt;a href="https://blog.google/technology/google-deepmind/gemini-computer-use-model/">announcement article&lt;/a>
and made available in public preview last month, via the Gemini API
on &lt;a href="https://aistudio.google.com/prompts/new_chat">Google AI Studio&lt;/a> and
&lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/computer-use">Vertex AI&lt;/a>,
Gemini 2.5 Pro introduces a pretty powerful &lt;a href="https://ai.google.dev/gemini-api/docs/computer-use">&amp;ldquo;Computer Use&amp;rdquo;&lt;/a> feature.&lt;/p></description><content:encoded>
<![CDATA[<p>In this article, I&rsquo;ll guide you through the process of programmatically interacting with a web browser
using the new <a href="https://ai.google.dev/gemini-api/docs/computer-use">Computer Use model</a> in Gemini 2.5 Pro.
We&rsquo;ll accomplish this in Java &#x2615; leveraging Microsoft&rsquo;s powerful <a href="https://playwright.dev/java/">Playwright Java SDK</a> to handle the browser automation.</p>
<h2 id="the-new-computer-use-model">The New Computer Use Model</h2>
<p>Unveiled in this <a href="https://blog.google/technology/google-deepmind/gemini-computer-use-model/">announcement article</a>
and made available in public preview last month, via the Gemini API
on <a href="https://aistudio.google.com/prompts/new_chat">Google AI Studio</a> and
<a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/computer-use">Vertex AI</a>,
Gemini 2.5 Pro introduces a pretty powerful <a href="https://ai.google.dev/gemini-api/docs/computer-use">&ldquo;Computer Use&rdquo;</a> feature.</p>
<p>This allows the model to understand and interact with a computer screen much like a human would.
It&rsquo;s a multimodal model that takes a screenshot of a web page as input and returns a sequence of actions to perform,
such as clicking buttons, filling out text fields, and navigating through pages,
until it reaches a certain goal set by the user.</p>
<p>The general flow is illustrated in the following diagram:</p>
<p><figure>
  <a href="#img-a5972b85b2298fe92ff466986e8dfa4a">
    <img src="/img/gemini/browser-use/computer-use-flow.png"
      alt="Gemini Computer Use flow"
       />
  </a>
  <figcaption>Gemini Computer Use flow</figcaption>
</figure>
<div class="lightbox" id="img-a5972b85b2298fe92ff466986e8dfa4a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/browser-use/computer-use-flow.png"
    alt="Gemini Computer Use flow"
     />
  <div class="lightbox-caption">Gemini Computer Use flow</div>
</div>
</p>
<p>Let&rsquo;s get the project set up, and then we&rsquo;ll see further down how this flow works, and how to implement it, using Java and Playwright.</p>
<h2 id="project-setup">Project Setup</h2>
<p>For this tutorial, I&rsquo;m using a straightforward Java project built with <a href="https://maven.apache.org/">Maven</a>.
We&rsquo;ll need two main dependencies: one for the <a href="https://central.sonatype.com/artifact/com.google.genai/google-genai">Gemini API</a>
and another for <a href="https://central.sonatype.com/artifact/com.microsoft.playwright/playwright">Playwright</a>.</p>
<p>Here&rsquo;s the relevant section of my <code>pom.xml</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependencies&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.genai<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-genai<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.24.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.microsoft.playwright<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>playwright<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.56.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependencies&gt;</span>
</span></span></code></pre></div><h2 id="getting-started-with-playwright">Getting Started with Playwright</h2>
<p>Now, let&rsquo;s dive into the code.
<a href="https://playwright.dev/java/">Playwright</a> is a library from Microsoft for automating browser actions,
and it&rsquo;s available for several languages, including Java.</p>
<p>Here’s a basic example of how to launch a browser and navigate to a page:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.microsoft.playwright.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">BrowserAutomation</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Playwright<span style="color:#bbb"> </span>playwright<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Playwright.<span style="color:#4070a0">create</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Browser<span style="color:#bbb"> </span>browser<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>playwright.<span style="color:#4070a0">chromium</span>().<span style="color:#4070a0">launch</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>BrowserType.<span style="color:#4070a0">LaunchOptions</span>().<span style="color:#4070a0">setHeadless</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Page<span style="color:#bbb"> </span>page<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>browser.<span style="color:#4070a0">newPage</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>page.<span style="color:#4070a0">navigate</span>(<span style="color:#4070a0">&#34;https://www.google.com&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// We&#39;ll add the Gemini integration here.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>browser.<span style="color:#4070a0">close</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>In this snippet, I&rsquo;m using the <a href="https://www.chromium.org/chromium-projects/">Chromium</a> browser that ships with Playwright.
But other browsers like <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> are also available.</p>
<p>I&rsquo;m launching it in <em>&ldquo;headless&rdquo;</em> mode, meaning no browser window will be visible.
This is ideal for automated tasks where visual inspection isn&rsquo;t necessary.
For debugging, however, you can set <code>setHeadless(false)</code> to watch the automation in real-time.</p>
<h2 id="integrating-with-gemini">Integrating with Gemini</h2>
<p>First, you&rsquo;ll need a Gemini API key, which you can obtain from <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a>.
Once you have your key, it&rsquo;s best practice to set it as an environment variable named <code>GEMINI_API_KEY</code>.
We&rsquo;ll use a try with resources block to create (and close automatically) the client to access Gemini:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>With the browser automation foundation in place, we can now integrate Gemini to steer it.
The process works as a loop: we send a prompt with a goal to reach to the model,
the model returns a series of actions, our code executes them using Playwright,
we share the screenshot of the current web page after each action,
and we repeat the cycle until the task is complete.</p>
<h3 id="the-agent-loop">The Agent Loop</h3>
<p>The Gemini documentation refers to this cyclical process as an <em>&ldquo;agent loop&rdquo;</em>:</p>
<ol>
<li><strong>Send Request</strong>: The loop begins by sending the user&rsquo;s prompt, the latest UI screenshot, and the enabled <code>computer_use</code> tool to the model.</li>
<li><strong>Receive Response</strong>: The model analyzes the inputs and returns a <code>function_call</code> suggesting a specific UI action.</li>
<li><strong>Execute Action</strong>: Our Java code parses this <code>function_call</code> and translates it into a Playwright command to be executed in the browser.</li>
<li><strong>Capture New State</strong>: After the action is performed, we capture a new screenshot and send it back to the model along with a <code>function_response</code>, starting the loop over.</li>
</ol>
<p>This process continues until the model determines the initial goal has been met.</p>
<h3 id="code-walkthrough">Code Walkthrough</h3>
<p>Let&rsquo;s examine the <code>BrowserUse.java</code> file to see how this loop is implemented.
(You&rsquo;ll find the full source code at the bottom of the page.)</p>
<p>First, we initialize Playwright and the Gemini client (as explained above),
setting a specific viewport size which is crucial for the coordinate scaling we&rsquo;ll discuss shortly:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Playwright<span style="color:#bbb"> </span>playwright<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Playwright.<span style="color:#4070a0">create</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>BrowserType<span style="color:#bbb"> </span>chromium<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>playwright.<span style="color:#4070a0">chromium</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Browser<span style="color:#bbb"> </span>browser<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chromium.<span style="color:#4070a0">launch</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>BrowserType.<span style="color:#4070a0">LaunchOptions</span>().<span style="color:#4070a0">setHeadless</span>(<span style="color:#007020;font-weight:bold">true</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>WIDTH<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>1000;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>HEIGHT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>1000;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>BrowserContext<span style="color:#bbb"> </span>context<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>browser.<span style="color:#4070a0">newContext</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Browser.<span style="color:#4070a0">NewContextOptions</span>().<span style="color:#4070a0">setViewportSize</span>(WIDTH,<span style="color:#bbb"> </span>HEIGHT));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Page<span style="color:#bbb"> </span>page<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>context.<span style="color:#4070a0">newPage</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// The agent loop will go here.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next, we kick off the conversation with the model by sending our initial prompt.
This defines the overall goal for the agent.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>Content<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>history<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Content<span style="color:#bbb"> </span>initialContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Find the tallest Stitch plushie under €100 on Amazon.fr
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    and then provide the link to that item.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>history.<span style="color:#4070a0">add</span>(initialContent);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>GenerateContentResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateContent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;gemini-2.5-computer-use-preview-10-2025&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>initialContent,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(Tool.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">computerUse</span>(ComputerUse.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">environment</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>Environment.<span style="color:#4070a0">Known</span>.<span style="color:#4070a0">ENVIRONMENT_BROWSER</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, we&rsquo;re using the <code>gemini-2.5-computer-use-preview-10-2025</code> model and explicitly enabling the <code>computer_use</code> tool.</p>
<p>The core of the application is a <code>while</code> loop that continues as long as the model returns function calls for us to execute:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(response.<span style="color:#4070a0">functionCalls</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#666">!</span>response.<span style="color:#4070a0">functionCalls</span>().<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We iterate over the function calls returned by the model and use a <code>switch</code> statement to execute the corresponding Playwright action.
After each interaction, we save a screenshot of the page, that we give back to the model to <em>see</em> the result of the action on the page.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(FunctionCall<span style="color:#bbb"> </span>functionCall<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>response.<span style="color:#4070a0">functionCalls</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">switch</span><span style="color:#bbb"> </span>(functionCall.<span style="color:#4070a0">name</span>().<span style="color:#4070a0">get</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;navigate_to_url&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>page.<span style="color:#4070a0">navigate</span>((String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;url&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;click_at&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... other cases</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This <code>switch</code> block is the heart of the integration, translating the model&rsquo;s intentions into concrete browser actions.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Supported Actions</span>
      </div>
      <div class="admonition-content">
        <p>You can check the list of
<a href="https://ai.google.dev/gemini-api/docs/computer-use#supported-actions">supported actions</a> in the documentation.
You don&rsquo;t necessarily have to implement them all, depending on your use case.
And there&rsquo;s a proposed <a href="https://github.com/google-gemini/computer-use-preview/blob/main/computers/playwright/playwright.py">implementation</a>
in Python that you can take inspiration from.
Note that there are often multiple ways to implement certain actions, like scrolling for example.
And you&rsquo;ll see in the full source code below how I implemented most of them in Java with Playwright.</p>
      </div>
    </div><h3 id="coordinate-scaling">Coordinate Scaling</h3>
<p>A critical detail in this process is coordinate handling.
The Gemini model operates on a normalized 1000x1000 grid, independent of the actual browser viewport size.
Therefore, we must scale the coordinates it provides to match our browser&rsquo;s dimensions.</p>
<p>In my case, I simply ended up using 1000x1000 for my browser window,
but I kept the scaling calculation in place, should I want to change the browser size in the future.</p>
<p>Here&rsquo;s the implementation for a <code>click_at</code> action:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;click_at&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>xClick<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;x&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>yClick<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;y&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>scaledCoordForClicking<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ScaledCoord(xClick,<span style="color:#bbb"> </span>yClick).<span style="color:#4070a0">scaleTo</span>(WIDTH,<span style="color:#bbb"> </span>HEIGHT);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>page.<span style="color:#4070a0">mouse</span>().<span style="color:#4070a0">click</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>scaledCoordForClicking.<span style="color:#4070a0">x</span>,<span style="color:#bbb"> </span>scaledCoordForClicking.<span style="color:#4070a0">y</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is accomplished with a simple <code>ScaledCoord</code> record:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">ScaledCoord</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>x,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>y)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ScaledCoord<span style="color:#bbb"> </span><span style="color:#06287e">scaleTo</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>width,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>height)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ScaledCoord(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>(<span style="color:#902000">int</span>)<span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">x</span><span style="color:#bbb"> </span><span style="color:#666">/</span><span style="color:#bbb"> </span>1000.<span style="color:#4070a0">0</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>width),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>(<span style="color:#902000">int</span>)<span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">y</span><span style="color:#bbb"> </span><span style="color:#666">/</span><span style="color:#bbb"> </span>1000.<span style="color:#4070a0">0</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>height)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="accepting-safety-decisions">Accepting Safety Decisions</h3>
<p>For certain actions, the model might require a confirmation before proceeding.
This is a safety feature to prevent unintended consequences, for example when dealing with sensitive information or performing critical operations.
The model will include a <code>safety_decision</code> field in its response, indicating that a confirmation is needed.
In a real-world application, you should prompt the user for their approval.
However, for the purpose of this demonstration, the code at the bottom of this article automatically acknowledges these safety decisions.
This is implemented by checking for the presence of the <code>safety_decision</code> field in the function call arguments
and then adding a <code>safety_acknowledgement</code> field with the value <code>true</code> to the function response, as you can see in the provided source code.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(FunctionCall<span style="color:#bbb"> </span>functionCall<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>response.<span style="color:#4070a0">functionCalls</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>args<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>functionCall.<span style="color:#4070a0">args</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>HashMap<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(args.<span style="color:#4070a0">containsKey</span>(<span style="color:#4070a0">&#34;safety_decision&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;safety_acknowledgement&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;true&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>This <code>safety_decision</code> request often comes when you have to accept things like cookies policy, and other pop-ups.
You can learn more about this in the <a href="https://ai.google.dev/gemini-api/docs/computer-use#safety-decisions">documentation</a>.</p>
<h3 id="closing-the-loop">Closing the Loop</h3>
<p>After executing an action, we must inform the model of the outcome.
This is done by adding a <code>function_response</code> containing the result to the conversation history.
We also give the model the screenshot, so it <em>sees</em> the updated page.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;url&#34;</span>,<span style="color:#bbb"> </span>page.<span style="color:#4070a0">url</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// wait to ensure the page has fully rendered after the action</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>sleep(1000);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>screenshot<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">screenshot</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// also saving the screenshot locally for debugging purpose</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Page.<span style="color:#4070a0">ScreenshotOptions</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setPath</span>(Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;screenshot-&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>index<span style="color:#666">++</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;.png&#34;</span>)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>history.<span style="color:#4070a0">add</span>(Content.<span style="color:#4070a0">fromParts</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromFunctionResponse</span>(functionCall.<span style="color:#4070a0">name</span>().<span style="color:#4070a0">get</span>(),<span style="color:#bbb"> </span>result),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromBytes</span>(screenshot,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>We then call <code>generateContent</code> again with the updated history,
including the <strong>result of the action</strong> as well as the <strong>screenshot of the page</strong>,
and the loop continues until the model determines the task is finished and stops returning function calls.
At that point, we can print the final text-based response.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span></code></pre></div>
    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>You <strong>must always take a screenshot after each action</strong>, and <strong>send it to the model</strong> each time.
Otherwise, the Computer Use model is in the dark, and doesn&rsquo;t know what&rsquo;s going on in the browser.</p>
      </div>
    </div><p>This concludes our tour of how to use the Gemini 2.5 Pro Computer Use model with Java and Playwright.
Now, before having a look at the full source code at the bottom of the article,
let&rsquo;s have a look at a few example use cases I tried.</p>
<h2 id="example-browser-use-requests">Example Browser Use Requests</h2>
<h3 id="playing-a-button-clicking-game">Playing a Button Clicking Game</h3>
<p>When working on implementing the Playwright Chromium functions to echo the actions requested by the model,
at first, I wasn&rsquo;t sure if my logic for handling button clicks was correct (in particular the coordinate scaling).
So I wanted to double check that it was working fine, and for that purpose,
I created a simple <a href="https://storage.googleapis.com/public-bucket-for-demos/button.html?ne">button clicking game</a>:
Each time you click on the red button, the button moves randomly on the page, and a score shows the number of clicks.
(Feel free to play it, it&rsquo;s boring stupid!)</p>
<p>I asked Gemini Computer Use to go to that game page, click the button 10 times, and respond with the final score at the end.
Logically, clicking 10 times would give a score of 10!</p>
<blockquote>
<p>Go to this red button clicking game page:
<a href="https://storage.googleapis.com/public-bucket-for-demos/button.html">https://storage.googleapis.com/public-bucket-for-demos/button.html</a>
Click 10 times the red button.
Return the score displayed in the top left hand corner.</p></blockquote>
<p>This little animated GIF shows you the button moving around and the score increasing:</p>

<details>
  <summary>Click to see the animation</summary>
  <p><figure>
  <a href="#img-26f429ebeac370693dca85a8a78378df">
    <img src="/img/gemini/browser-use/animation-red-button.gif"
      alt="button clicking game animation"
       />
  </a>
  <figcaption>button clicking game animation</figcaption>
</figure>
<div class="lightbox" id="img-26f429ebeac370693dca85a8a78378df">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/browser-use/animation-red-button.gif"
    alt="button clicking game animation"
     />
  <div class="lightbox-caption">button clicking game animation</div>
</div>
</p>
</details>

<p>Here&rsquo;s the model&rsquo;s final answer:</p>
<blockquote>
<p>I have evaluated step 12. I clicked the button and the score increased to 10.
I have clicked the button 10 times and the score is now 10, as displayed in the top left corner.</p></blockquote>
<h3 id="searching-an-article-on-my-blog">Searching an Article on my blog</h3>
<p>I asked Gemini Computer Use to find a particular article on my blog:</p>
<blockquote>
<p>Find an article on glaforge.dev on
how to use Nano Banana in an ADK agent</p></blockquote>
<p>Let&rsquo;s see that in action in this other animation:</p>

<details>
  <summary>Click to see the animation</summary>
  <p><figure>
  <a href="#img-cee723415ca2c2e244ee427c4968a19a">
    <img src="/img/gemini/browser-use/animation-blog-article-search.gif"
      alt="Searching an article on my blog"
       />
  </a>
  <figcaption>Searching an article on my blog</figcaption>
</figure>
<div class="lightbox" id="img-cee723415ca2c2e244ee427c4968a19a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/browser-use/animation-blog-article-search.gif"
    alt="Searching an article on my blog"
     />
  <div class="lightbox-caption">Searching an article on my blog</div>
</div>
</p>
</details>

<p>Here&rsquo;s the model&rsquo;s final answer:</p>
<pre tabindex="0"><code>I have evaluated step 18 and scrolled down further. The article
presents the code for `NanoBananaCreativeAgent` and highlights
the line `.model(&#34;gemini-2.5-flash-image-preview&#34;)` as key for
telling the ADK to route requests to the Nano Banana
(Gemini 2.5 Flash Image) model for image generation within the agent.

It also mentions an `.afterModelCallback` part that it will zoom
in on. This article clearly explains how to configure the ADK
agent to use the Nano Banana model.

I have found the information requested.

The article &#34;Creative Java AI agents with ADK and Nano Banana&#34; on
glaforge.dev explains how to use the &#34;Nano Banana&#34; model
(gemini-2.5-flash-image-preview) within an ADK agent.

It is done by specifying `.model(&#34;gemini-2.5-flash-image-preview&#34;)`
when building the `LlmAgent` using `LlmAgent.builder()`
within the agent&#39;s definition.
The article also goes into handling the image output
using `.afterModelCallback`.
</code></pre><h3 id="finding-an-item-on-amazon">Finding an Item on Amazon</h3>
<p>This time, I asked Gemini Computer Use:</p>
<blockquote>
<p>Find the tallest Stitch plushie under €100 on Amazon.fr
and give me the URL of the product.
Be sure it&rsquo;s not Angel the pink one.</p></blockquote>
<p>It found a nice Stitch plushie!</p>

<details>
  <summary>Click to see the animation</summary>
  <p><figure>
  <a href="#img-40bf26488b2747b51a6ffd648cb16d8d">
    <img src="/img/gemini/browser-use/animation-stitch.gif"
      alt="A big Stitch plushie found on Amazon"
       />
  </a>
  <figcaption>A big Stitch plushie found on Amazon</figcaption>
</figure>
<div class="lightbox" id="img-40bf26488b2747b51a6ffd648cb16d8d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/browser-use/animation-stitch.gif"
    alt="A big Stitch plushie found on Amazon"
     />
  <div class="lightbox-caption">A big Stitch plushie found on Amazon</div>
</div>
</p>
</details>

<h2 id="the-full-source-code">The Full Source Code</h2>
<p>Tim to reveal the complete source code!
Feel free to adapt it to your needs, or to further expand the browser actions supported.</p>

<details>
  <summary>Click to see the full source code</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.Client</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.microsoft.playwright.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.URLEncoder</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.nio.charset.StandardCharsets</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.nio.file.Paths</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.ArrayList</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.HashMap</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.List</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.Map</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Playwright<span style="color:#bbb"> </span>playwright<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Playwright.<span style="color:#4070a0">create</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>BrowserType<span style="color:#bbb"> </span>chromium<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>playwright.<span style="color:#4070a0">chromium</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Browser<span style="color:#bbb"> </span>browser<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chromium.<span style="color:#4070a0">launch</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>BrowserType.<span style="color:#4070a0">LaunchOptions</span>().<span style="color:#4070a0">setHeadless</span>(<span style="color:#007020;font-weight:bold">true</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>WIDTH<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>1000;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>HEIGHT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>1000;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>BrowserContext<span style="color:#bbb"> </span>context<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>browser.<span style="color:#4070a0">newContext</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Browser.<span style="color:#4070a0">NewContextOptions</span>().<span style="color:#4070a0">setViewportSize</span>(WIDTH,<span style="color:#bbb"> </span>HEIGHT));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Page<span style="color:#bbb"> </span>page<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>context.<span style="color:#4070a0">newPage</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>List<span style="color:#666">&lt;</span>Content<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>history<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Content<span style="color:#bbb"> </span>initialContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                    Find an article on glaforge.dev on how to use Nano Banana in an ADK agent
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                    &#34;&#34;&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>history.<span style="color:#4070a0">add</span>(initialContent);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>GenerateContentResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>index<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateContent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#4070a0">&#34;gemini-2.5-computer-use-preview-10-2025&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>history,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>GenerateContentConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">tools</span>(Tool.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>.<span style="color:#4070a0">computerUse</span>(ComputerUse.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>.<span style="color:#4070a0">environment</span>(Environment.<span style="color:#4070a0">Known</span>.<span style="color:#4070a0">ENVIRONMENT_BROWSER</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(response.<span style="color:#4070a0">functionCalls</span>()<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">||</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">functionCalls</span>().<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#666">!</span>response.<span style="color:#4070a0">candidates</span>().<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>history.<span style="color:#4070a0">add</span>(response.<span style="color:#4070a0">candidates</span>().<span style="color:#4070a0">get</span>().<span style="color:#4070a0">get</span>(0).<span style="color:#4070a0">content</span>().<span style="color:#4070a0">get</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(FunctionCall<span style="color:#bbb"> </span>functionCall<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>response.<span style="color:#4070a0">functionCalls</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>args<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>functionCall.<span style="color:#4070a0">args</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;==&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>functionCall.<span style="color:#4070a0">name</span>().<span style="color:#4070a0">get</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>args);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>HashMap<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(args.<span style="color:#4070a0">containsKey</span>(<span style="color:#4070a0">&#34;safety_decision&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;safety_acknowledgement&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;true&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">switch</span><span style="color:#bbb"> </span>(functionCall.<span style="color:#4070a0">name</span>().<span style="color:#4070a0">get</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;open_web_browser&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;navigate&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;navigate_to_url&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>page.<span style="color:#4070a0">navigate</span>((String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;url&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;go_back&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>page.<span style="color:#4070a0">goBack</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;get_page_content&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>String<span style="color:#bbb"> </span>pageContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">content</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(pageContent.<span style="color:#4070a0">length</span>()<span style="color:#bbb"> </span><span style="color:#666">&gt;</span><span style="color:#bbb"> </span>20000)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>pageContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>pageContent.<span style="color:#4070a0">substring</span>(0,<span style="color:#bbb"> </span>20000);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;content&#34;</span>,<span style="color:#bbb"> </span>pageContent);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;click_element&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">click</span>((String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;selector&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(PlaywrightException<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;error&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;message&#34;</span>,<span style="color:#bbb"> </span>e.<span style="color:#4070a0">getMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;click_at&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>xClick<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;x&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>yClick<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;y&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>scaledCoordForClicking<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ScaledCoord(xClick,<span style="color:#bbb"> </span>yClick).<span style="color:#4070a0">scaleTo</span>(WIDTH,<span style="color:#bbb"> </span>HEIGHT);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>page.<span style="color:#4070a0">mouse</span>().<span style="color:#4070a0">click</span>(scaledCoordForClicking.<span style="color:#4070a0">x</span>,<span style="color:#bbb"> </span>scaledCoordForClicking.<span style="color:#4070a0">y</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;type_text&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>String<span style="color:#bbb"> </span>text<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;text&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>String<span style="color:#bbb"> </span>selectorForTyping<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;selector&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(selectorForTyping<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">locator</span>(selectorForTyping).<span style="color:#4070a0">type</span>(text);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">keyboard</span>().<span style="color:#4070a0">type</span>(text);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;type_text_at&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>String<span style="color:#bbb"> </span>text_to_type<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;text&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">boolean</span><span style="color:#bbb"> </span>press_enter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(Boolean)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">getOrDefault</span>(<span style="color:#4070a0">&#34;press_enter&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>xType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;x&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>yType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;y&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>scaledCoordForTyping<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ScaledCoord(xType,<span style="color:#bbb"> </span>yType).<span style="color:#4070a0">scaleTo</span>(WIDTH,<span style="color:#bbb"> </span>HEIGHT);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>page.<span style="color:#4070a0">mouse</span>().<span style="color:#4070a0">click</span>(scaledCoordForTyping.<span style="color:#4070a0">x</span>,<span style="color:#bbb"> </span>scaledCoordForTyping.<span style="color:#4070a0">y</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>page.<span style="color:#4070a0">keyboard</span>().<span style="color:#4070a0">type</span>(text_to_type);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(press_enter)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">keyboard</span>().<span style="color:#4070a0">press</span>(<span style="color:#4070a0">&#34;Enter&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;scroll_document&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>String<span style="color:#bbb"> </span>direction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;direction&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>magnitude<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>800;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(args.<span style="color:#4070a0">containsKey</span>(<span style="color:#4070a0">&#34;magnitude&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>magnitude<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>((Number)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;magnitude&#34;</span>)).<span style="color:#4070a0">intValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#4070a0">&#34;down&#34;</span>.<span style="color:#4070a0">equals</span>(direction))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">evaluate</span>(<span style="color:#4070a0">&#34;window.scrollBy(0, &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>magnitude<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;)&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#4070a0">&#34;up&#34;</span>.<span style="color:#4070a0">equals</span>(direction))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">evaluate</span>(<span style="color:#4070a0">&#34;window.scrollBy(0, -&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>magnitude<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;)&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;search&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>String<span style="color:#bbb"> </span>query<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>args.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;query&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(query<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>query.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>page.<span style="color:#4070a0">navigate</span>(<span style="color:#4070a0">&#34;https://www.google.com/search?q=&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                        </span>URLEncoder.<span style="color:#4070a0">encode</span>(query,<span style="color:#bbb"> </span>StandardCharsets.<span style="color:#4070a0">UTF_8</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;unsupported function&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;message&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;search function requires a query argument.&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;take_screenshot&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>screenshotBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">screenshot</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;screenshot-image-bytes&#34;</span>,<span style="color:#bbb"> </span>screenshotBytes);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;wait_5_seconds&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>sleep(5000);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;success&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">default</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;error&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;unsupported function&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>result.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;url&#34;</span>,<span style="color:#bbb"> </span>page.<span style="color:#4070a0">url</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>sleep(1000);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>screenshot<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>page.<span style="color:#4070a0">screenshot</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Page.<span style="color:#4070a0">ScreenshotOptions</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                </span>.<span style="color:#4070a0">setPath</span>(Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;screenshot-&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>index<span style="color:#666">++</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;.png&#34;</span>)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>history.<span style="color:#4070a0">add</span>(Content.<span style="color:#4070a0">fromParts</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>Part.<span style="color:#4070a0">fromFunctionResponse</span>(functionCall.<span style="color:#4070a0">name</span>().<span style="color:#4070a0">get</span>(),<span style="color:#bbb"> </span>result),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>Part.<span style="color:#4070a0">fromBytes</span>(screenshot,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">finally</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>browser.<span style="color:#4070a0">close</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">ScaledCoord</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>x,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>y)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ScaledCoord<span style="color:#bbb"> </span><span style="color:#06287e">scaleTo</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>width,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>height)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ScaledCoord(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>(<span style="color:#902000">int</span>)<span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">x</span><span style="color:#bbb"> </span><span style="color:#666">/</span><span style="color:#bbb"> </span>1000.<span style="color:#4070a0">0</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>width),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>(<span style="color:#902000">int</span>)<span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">y</span><span style="color:#bbb"> </span><span style="color:#666">/</span><span style="color:#bbb"> </span>1000.<span style="color:#4070a0">0</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>height)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">sleep</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>milliseconds)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Thread.<span style="color:#4070a0">sleep</span>(milliseconds);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(InterruptedException<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// do nothing</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<h2 id="conclusion">Conclusion</h2>
<p>My experiments with the Gemini 2.5 Pro Computer Use model have been insightful, revealing both its potential and some of its limitations.</p>
<p>One of the first things you&rsquo;ll notice is the <strong>pacing</strong>.
Each turn in the agent loop, model call, and action execution, takes time.
A multi-step task like finding a product on an e-commerce site requires patience, as the process unfolds deliberately, one step at a time.
It&rsquo;s a good idea to wait a second before taking a screenshot, to be sure the page has fully rendered after the last action.
Otherwise you could feed a blank or half-blank screen back to the model, which won&rsquo;t be helpful.
So you would use a computer-use model probably more for asynchronous background tasks, and not for an immediate response.</p>
<p>A significant real-world challenge is the prevalence of <strong>cookie consent banners and other pop-ups</strong>.
But the model is able to click the right <em>&ldquo;accept&rdquo;</em> buttons here and there, to get rid of them, and focus on the task at hand.
Captchas can also get in the way, because websites notice that this is an automated agent at play.
But there&rsquo;s a way to <a href="https://ai.google.dev/gemini-api/docs/computer-use#safety-decisions">automate what they call <em>safety decisions</em></a>
(which is what I implemented in the code, by auto-acknowledgement).</p>
<p>Interestingly, in one of my many experiments, in moments of apparent frustration,
the model decided to <strong>abandon the target website and default to a Google search</strong>,
to find the information on the website it couldn&rsquo;t find by scrolling and clicking around!</p>
<p>In summary, once you acknowledge some of the potential challenges, this is clearly a fascinating technology with a promising <em>agentic</em> future!
Imagine web agents able to handle mundane but boring web-based tasks for you, saving you precious time?
That&rsquo;s definitely something worth investigating!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Javelit frontend for an ADK agent</title><link>https://glaforge.dev/posts/2025/10/26/a-javelit-frontend-for-an-adk-agent/</link><pubDate>Sun, 26 Oct 2025 18:06:13 +0100</pubDate><guid>https://glaforge.dev/posts/2025/10/26/a-javelit-frontend-for-an-adk-agent/</guid><description>&lt;p>Continuing my journey with &lt;a href="https://javelit.io">Javelit&lt;/a>,
after creating a &lt;a href="https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/">frontend for &lt;em>&amp;ldquo;Nano Banana&amp;rdquo;&lt;/em> to generate images&lt;/a>
and a &lt;a href="https://glaforge.dev/posts/2025/10/25/creating-a-javelit-chat-interface-for-langchain4j/">chat interface for a LangChain4j-based Gemini chat model&lt;/a>,
I decided to see how I could integrate an &lt;a href="https://github.com/google/adk-java">ADK&lt;/a> agent with a Javelit frontend.&lt;/p>
&lt;h2 id="the-javelit-interface-for-an-adk-search-agent">The Javelit interface for an ADK search agent&lt;/h2>
&lt;p>&lt;figure>
&lt;a href="#img-03ed9e9178afe3608f5ea337720e6971">
&lt;img src="https://glaforge.dev/img/adk/adk-javelit.png"
alt="A Javelit interface for an ADK search agent"
/>
&lt;/a>
&lt;figcaption>A Javelit interface for an ADK search agent&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-03ed9e9178afe3608f5ea337720e6971">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/adk/adk-javelit.png"
alt="A Javelit interface for an ADK search agent"
/>
&lt;div class="lightbox-caption">A Javelit interface for an ADK search agent&lt;/div>
&lt;/div>
&lt;/p></description><content:encoded>
<![CDATA[<p>Continuing my journey with <a href="https://javelit.io">Javelit</a>,
after creating a <a href="https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/">frontend for <em>&ldquo;Nano Banana&rdquo;</em> to generate images</a>
and a <a href="https://glaforge.dev/posts/2025/10/25/creating-a-javelit-chat-interface-for-langchain4j/">chat interface for a LangChain4j-based Gemini chat model</a>,
I decided to see how I could integrate an <a href="https://github.com/google/adk-java">ADK</a> agent with a Javelit frontend.</p>
<h2 id="the-javelit-interface-for-an-adk-search-agent">The Javelit interface for an ADK search agent</h2>
<p><figure>
  <a href="#img-03ed9e9178afe3608f5ea337720e6971">
    <img src="/img/adk/adk-javelit.png"
      alt="A Javelit interface for an ADK search agent"
       />
  </a>
  <figcaption>A Javelit interface for an ADK search agent</figcaption>
</figure>
<div class="lightbox" id="img-03ed9e9178afe3608f5ea337720e6971">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/adk-javelit.png"
    alt="A Javelit interface for an ADK search agent"
     />
  <div class="lightbox-caption">A Javelit interface for an ADK search agent</div>
</div>
</p>
<p>The key ingredients of this interface:</p>
<ul>
<li>a title <em>(with some emojis &#x1f603;)</em></li>
<li>a container that displays the agent&rsquo;s answer</li>
<li>a text input field to enter the search query</li>
</ul>
<h2 id="the-adk-agent">The ADK agent</h2>
<p>For the purpose of this article, I built a simple search agent, with a couple of search tools:</p>
<ul>
<li>a <strong>Google Search</strong> tool</li>
<li>a <strong>Google Maps</strong> tool</li>
</ul>
<p>So you can search for up-to-date information on Google Search,
as well as details about landmarks, points of interest, restaurants, etc., via Google Maps.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>I recently <a href="https://github.com/google/adk-java/pull/534">contributed</a>
the <code>GoogleMapsTool</code> to ADK for Java, so it&rsquo;s not yet available in a public version,
you&rsquo;ll have to wait for the next release (or even build it from sources!)
to be able to use it.</p>
      </div>
    </div><p>Let&rsquo;s have a closer look at the agent code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;gemini-search-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful search assistant,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        able to search the web and Google Maps.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When a user asks for research,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        be sure to use the appropriate tools detailed below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `google_search` tool
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to search for up-to-date information.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `google_maps` tool
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to search for geographical information.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleMapsTool()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is a simple agent, with instructions detailing the tools at its disposal and wiring the two tools.</p>
<p>Next, to interact with the agent, we need some setup: we&rsquo;ll need a <code>Runner</code> and prepare a <code>Session</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>InMemorySessionService<span style="color:#bbb"> </span>sessionService<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemorySessionService();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>InMemoryArtifactService<span style="color:#bbb"> </span>artifactService<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemoryArtifactService();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Runner<span style="color:#bbb"> </span>runner<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Runner(agent,<span style="color:#bbb"> </span>agent.<span style="color:#4070a0">name</span>(),<span style="color:#bbb"> </span>artifactService,<span style="color:#bbb"> </span>sessionService,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>appName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner.<span style="color:#4070a0">appName</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>userId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>UUID.<span style="color:#4070a0">randomUUID</span>().<span style="color:#4070a0">toString</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Session<span style="color:#bbb"> </span>session<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">sessionService</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">createSession</span>(appName,<span style="color:#bbb"> </span>userId)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">blockingGet</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>To interact with this agent via the <code>Runner</code>&rsquo;s <code>runAsync()</code> method,
we need to keep the agent, the session, and the user ID around, so I created a <code>record</code> to hold them,
and created a method to wrap it all:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">AgentRunnerSession</span>(Runner<span style="color:#bbb"> </span>runner,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>userId,<span style="color:#bbb"> </span>Session<span style="color:#bbb"> </span>session)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>AgentRunnerSession<span style="color:#bbb"> </span><span style="color:#06287e">getAgentSession</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">//... agent definition above...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>AgentRunnerSession(runner,<span style="color:#bbb"> </span>userId,<span style="color:#bbb"> </span>session);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="building-the-ui-and-saving-the-agent-in-the-javelit-session">Building the UI and saving the agent in the Javelit session</h2>
<p>Like in the previous articles, the UI code layout is prepared in the class&rsquo; <code>main</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#60a0b0;font-style:italic">// Javelit UI layout</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>With Javelit, the UI component and layout code is re-run each time there&rsquo;s an interaction from the user.
In order to keep the conversation going with the agent, we need to store it in Javelit&rsquo;s session state.
It&rsquo;s created the first time thanks to the <code>computeIfAbsent()</code> method and retrieved upon subsequent calls:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>AgentRunnerSession<span style="color:#bbb"> </span>holder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(AgentRunnerSession)<span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">computeIfAbsent</span>(<span style="color:#4070a0">&#34;agentRunnerSession&#34;</span>,<span style="color:#bbb"> </span>key<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>getAgentSession());<span style="color:#bbb">
</span></span></span></code></pre></div><p>We add the title component, a container to hold the agent&rsquo;s response, and a text input field for the user&rsquo;s search query:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;\uD83D\uDD0D ADK Search Agent \uD83E\uDD16\uD83E\uDDE0&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>JtContainer<span style="color:#bbb"> </span>eventContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">container</span>().<span style="color:#4070a0">border</span>(<span style="color:#007020;font-weight:bold">true</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>searchQuery<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textInput</span>(<span style="color:#4070a0">&#34;Search query&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>When the user interacts with this text input field by hitting Enter, the input value is saved in the <code>searchQuery</code> variable.
Once we have that user query, we can pass it to the agent via the <code>Runner</code>&rsquo;s <code>runAsync()</code> method.
For each event, we add a Markdown element with the content of that event:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(searchQuery<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>searchQuery.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>holder.<span style="color:#4070a0">runner</span>().<span style="color:#4070a0">runAsync</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>holder.<span style="color:#4070a0">userId</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>holder.<span style="color:#4070a0">session</span>().<span style="color:#4070a0">id</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(searchQuery)))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">blockingForEach</span>(event<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Jt.<span style="color:#4070a0">markdown</span>(event.<span style="color:#4070a0">stringifyContent</span>()).<span style="color:#4070a0">use</span>(eventContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>With this approach, we maintain the conversational state.
Although the UI isn&rsquo;t displaying the past requests and responses,
each time the user enters a query, both the query and response are kept in the agent&rsquo;s memory.
That way, if you ask for information about a restaurant, then you ask about opening times,
it remembers it&rsquo;s about this particular restaurant.</p>
<h2 id="the-whole-example">The whole example</h2>

<details>
  <summary>Click to view the whole source code</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">/// usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS com.google.adk:google-adk:0.3.1-SNAPSHOT</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">adk</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.agents.LlmAgent</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.artifacts.InMemoryArtifactService</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.runner.Runner</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.sessions.InMemorySessionService</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.sessions.Session</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.tools.GoogleMapsTool</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.tools.GoogleSearchTool</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Content</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Part</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.javelit.core.Jt</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.javelit.core.JtContainer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.UUID</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">App</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AgentRunnerSession<span style="color:#bbb"> </span>holder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(AgentRunnerSession)<span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">computeIfAbsent</span>(<span style="color:#4070a0">&#34;holder&#34;</span>,<span style="color:#bbb"> </span>key<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>getAgentSession());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;\uD83D\uDD0D ADK Search Agent \uD83E\uDD16\uD83E\uDDE0&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>JtContainer<span style="color:#bbb"> </span>eventContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">container</span>().<span style="color:#4070a0">border</span>(<span style="color:#007020;font-weight:bold">true</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>searchQuery<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textInput</span>(<span style="color:#4070a0">&#34;Search query&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(searchQuery<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>searchQuery.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>holder.<span style="color:#4070a0">runner</span>().<span style="color:#4070a0">runAsync</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>holder.<span style="color:#4070a0">userId</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>holder.<span style="color:#4070a0">session</span>().<span style="color:#4070a0">id</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(searchQuery))).<span style="color:#4070a0">blockingForEach</span>(event<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">markdown</span>(event.<span style="color:#4070a0">stringifyContent</span>()).<span style="color:#4070a0">use</span>(eventContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">AgentRunnerSession</span>(Runner<span style="color:#bbb"> </span>runner,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>userId,<span style="color:#bbb"> </span>Session<span style="color:#bbb"> </span>session)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>AgentRunnerSession<span style="color:#bbb"> </span><span style="color:#06287e">getAgentSession</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;gemini-search-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            You are a helpful search assistant, able to search the web and Google Maps.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            When a user asks for research, be sure to use the appropriate tools detailed below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Use the `google_search` tool to search for up-to-date information.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Use the `google_maps` tool to search for geographical information.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleMapsTool()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>InMemorySessionService<span style="color:#bbb"> </span>sessionService<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemorySessionService();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>InMemoryArtifactService<span style="color:#bbb"> </span>artifactService<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemoryArtifactService();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Runner<span style="color:#bbb"> </span>runner<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Runner(agent,<span style="color:#bbb"> </span>agent.<span style="color:#4070a0">name</span>(),<span style="color:#bbb"> </span>artifactService,<span style="color:#bbb"> </span>sessionService,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>appName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner.<span style="color:#4070a0">appName</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>userId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>UUID.<span style="color:#4070a0">randomUUID</span>().<span style="color:#4070a0">toString</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Session<span style="color:#bbb"> </span>session<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">sessionService</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">createSession</span>(appName,<span style="color:#bbb"> </span>userId)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">blockingGet</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>AgentRunnerSession(runner,<span style="color:#bbb"> </span>userId,<span style="color:#bbb"> </span>session);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<h2 id="summary">Summary</h2>
<p>Another <a href="https://javelit.io/">Javelit</a> integration on the books, this time with <a href="https://github.com/google/adk-java">ADK for Java</a>.</p>
<p>At first, I was a bit surprised by the library&rsquo;s unusual approach,
compared to more event-driven or reactive web frameworks.
However, I&rsquo;m liking the simplicity of Javelit for quickly building a web frontend to let me experiment with ideas.</p>
<p>There are still many more features or components I&rsquo;d like to explore (like charts, multi-page components, etc.)
So you might see me write a few more articles, or use it more in my own presentations and workshops!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Creating a Javelit chat interface for LangChain4j</title><link>https://glaforge.dev/posts/2025/10/25/creating-a-javelit-chat-interface-for-langchain4j/</link><pubDate>Sat, 25 Oct 2025 17:11:19 +0200</pubDate><guid>https://glaforge.dev/posts/2025/10/25/creating-a-javelit-chat-interface-for-langchain4j/</guid><description>&lt;p>Yesterday, I uncovered the &lt;a href="https://javelit.io">Javelit&lt;/a> project in this
&lt;a href="https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/">article&lt;/a>
where I built a small frontend to create and edit images
with Google&amp;rsquo;s &lt;a href="https://aistudio.google.com/models/gemini-2-5-flash-image">Nano Banana&lt;/a> image model.&lt;/p>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition info">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">&lt;path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>&lt;/svg>
&lt;span>Javelit&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>&lt;strong>Javelit&lt;/strong> is an open source project inspired by Streamlit from the Python ecosystem
to enable rapid prototyping and deployment of applications in Java.&lt;/p></description><content:encoded>
<![CDATA[<p>Yesterday, I uncovered the <a href="https://javelit.io">Javelit</a> project in this
<a href="https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/">article</a>
where I built a small frontend to create and edit images
with Google&rsquo;s <a href="https://aistudio.google.com/models/gemini-2-5-flash-image">Nano Banana</a> image model.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Javelit</span>
      </div>
      <div class="admonition-content">
        <p><strong>Javelit</strong> is an open source project inspired by Streamlit from the Python ecosystem
to enable rapid prototyping and deployment of applications in Java.</p>
      </div>
    </div><p>Today, I want to show you another example of Javelit.
This time, I&rsquo;m creating a <strong>chat interface</strong> using <a href="https://docs.langchain4j.dev">LangChain4j</a>
with the <a href="https://docs.langchain4j.dev/integrations/language-models/google-ai-gemini">Gemini</a> chat model.</p>
<h2 id="what-we-want-to-build">What we want to build</h2>
<p><figure>
  <a href="#img-49a1f749c1fb4732aee527e40bbea6c1">
    <img src="/img/misc/javelit-langchain4j.png"
      alt="Generative AI chat interface built with Javelit, LangChain4j, and the Gemini model"
       />
  </a>
  <figcaption>Generative AI chat interface built with Javelit, LangChain4j, and the Gemini model</figcaption>
</figure>
<div class="lightbox" id="img-49a1f749c1fb4732aee527e40bbea6c1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/javelit-langchain4j.png"
    alt="Generative AI chat interface built with Javelit, LangChain4j, and the Gemini model"
     />
  <div class="lightbox-caption">Generative AI chat interface built with Javelit, LangChain4j, and the Gemini model</div>
</div>
</p>
<p>Notice how we alternate user and AI messages, and how the text is nicely rendered from Markdown?
Let&rsquo;s see how to implement such an interface with Javelit and LangChain4j.</p>
<h2 id="lets-build-it">Let&rsquo;s build it!</h2>
<p>Feel free to use any LLM model provider, but in my example today, I&rsquo;m using Gemini:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>ChatModel<span style="color:#bbb"> </span>CHAT_MODEL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>When using LangChain4j chat models at the low-level (not using <code>AiServices</code> or the new <em>agentic</em> module),
we keep track of chat messages via a simple <code>List</code> of <code>ChatMessage</code>.
This chat history needs to be stored in Javelit&rsquo;s session state:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>chatHistory<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span>)<span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">computeIfAbsent</span>(<span style="color:#4070a0">&#34;chatHistory&#34;</span>,<span style="color:#bbb"> </span>(key)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s give this application a title, and prepare a <em>container</em> that will receive all the chat messages from both the AI and the user:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;:coffee::parrot: LangChain4j Chat :speech_balloon:&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>JtContainer<span style="color:#bbb"> </span>msgContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">container</span>().<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice that Javelit supports emoji code names!</p>
<p>Next, let&rsquo;s append all the messages from the chat history to the message container, alternating between AI and user messages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(ChatMessage<span style="color:#bbb"> </span>message<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>chatHistory)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">switch</span><span style="color:#bbb"> </span>(message.<span style="color:#4070a0">type</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>USER<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;:speech_balloon: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>((UserMessage)<span style="color:#bbb"> </span>message).<span style="color:#4070a0">singleText</span>()).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>AI<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;:robot: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>((AiMessage)<span style="color:#bbb"> </span>message).<span style="color:#4070a0">text</span>()).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Again, we&rsquo;re using the <code>Jt.markdown()</code> component that outputs Markdown.
Which is nice since LLMs love to return Markdown responses!</p>
<p>It&rsquo;s time to get some input message from the user, with the <code>Jt.textInput()</code> component, and save its value in a local variable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>inputMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textInput</span>(<span style="color:#4070a0">&#34;Your message:&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s update the chat history, display the user&rsquo;s message, call the Gemini chat model, and then display its response:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(inputMessage<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>inputMessage.<span style="color:#4070a0">trim</span>().<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>chatHistory.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(inputMessage));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;:speech_balloon: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>inputMessage).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ChatResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>CHAT_MODEL.<span style="color:#4070a0">chat</span>(chatHistory);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>chatHistory.<span style="color:#4070a0">add</span>(response.<span style="color:#4070a0">aiMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;:robot: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>()).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And that&rsquo;s it!
When the user hits the enter key, after having typed its message, it retriggers a UI refresh.
It goes over all the UI component rendering again, but uses the state to show the alternation of user/AI messages.</p>
<h2 id="the-whole-source-code">The whole source code</h2>

<details>
  <summary>Click to view the whole source code</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">/// usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS dev.langchain4j:langchain4j-core:1.8.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS dev.langchain4j:langchain4j-google-ai-gemini:1.8.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.data.message.AiMessage</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.data.message.ChatMessage</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.data.message.UserMessage</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.chat.ChatModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.chat.response.ChatResponse</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.googleai.GoogleAiGeminiChatModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.javelit.core.Jt</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.javelit.core.JtContainer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.ArrayList</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.List</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">App</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>ChatModel<span style="color:#bbb"> </span>CHAT_MODEL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>chatHistory<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span>)<span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">computeIfAbsent</span>(<span style="color:#4070a0">&#34;chatHistory&#34;</span>,<span style="color:#bbb"> </span>(key)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;:coffee::parrot: LangChain4j Chat :speech_balloon:&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>JtContainer<span style="color:#bbb"> </span>msgContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">container</span>().<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(ChatMessage<span style="color:#bbb"> </span>message<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>chatHistory)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">switch</span><span style="color:#bbb"> </span>(message.<span style="color:#4070a0">type</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>USER<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;:speech_balloon: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>((UserMessage)<span style="color:#bbb"> </span>message).<span style="color:#4070a0">singleText</span>()).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span>AI<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;:robot: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>((AiMessage)<span style="color:#bbb"> </span>message).<span style="color:#4070a0">text</span>()).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>inputMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textInput</span>(<span style="color:#4070a0">&#34;Your message:&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(inputMessage<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span><span style="color:#666">!</span>inputMessage.<span style="color:#4070a0">trim</span>().<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>chatHistory.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(inputMessage));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;:speech_balloon: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>inputMessage).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>ChatResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>CHAT_MODEL.<span style="color:#4070a0">chat</span>(chatHistory);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>chatHistory.<span style="color:#4070a0">add</span>(response.<span style="color:#4070a0">aiMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;:robot: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>()).<span style="color:#4070a0">use</span>(msgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<p>Then you can run this class (after having <a href="https://docs.javelit.io/get-started/installation">installed Javelit</a>) with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>javelit run App.java
</span></span></code></pre></div><h2 id="summary">Summary</h2>
<p>And voilà!
In this article, we&rsquo;ve managed to build a <strong>simple chat UI</strong> for
<a href="https://docs.langchain4j.dev">LangChain4j</a> chat models using the
<a href="https://javelit.io">Javelit</a> UI toolkit.
We took advantage of Javelit&rsquo;s state management, as well as the built-in markdown rendering,
as LLMs generally use markdown in their responses.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Javelit to create quick interactive app frontends in Java</title><link>https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/</link><pubDate>Fri, 24 Oct 2025 17:33:27 +0200</pubDate><guid>https://glaforge.dev/posts/2025/10/24/javelit-to-create-quick-interactive-app-frontends-in-java/</guid><description>&lt;p>Have you ever heard of &lt;a href="https://javelit.io/">Javelit&lt;/a>?
It&amp;rsquo;s &lt;strong>like Streamlit&lt;/strong> in the Python ecosystem, but &lt;strong>for the Java developer&lt;/strong>!
I was lucky that the project creator reached out and introduced me to this cool little tool!&lt;/p>
&lt;p>Javelit is a tool to &lt;strong>quickly build interactive app frontends in Java&lt;/strong>, particularly for data apps, but it&amp;rsquo;s not limited to them.
It helps you quickly develop rapid prototypes, with a &lt;strong>live-reload&lt;/strong> loop, so that you can quickly experiment and update the app instantly.&lt;/p></description><content:encoded>
<![CDATA[<p>Have you ever heard of <a href="https://javelit.io/">Javelit</a>?
It&rsquo;s <strong>like Streamlit</strong> in the Python ecosystem, but <strong>for the Java developer</strong>!
I was lucky that the project creator reached out and introduced me to this cool little tool!</p>
<p>Javelit is a tool to <strong>quickly build interactive app frontends in Java</strong>, particularly for data apps, but it&rsquo;s not limited to them.
It helps you quickly develop rapid prototypes, with a <strong>live-reload</strong> loop, so that you can quickly experiment and update the app instantly.</p>
<p>The way it works (and thus the way you program with it) is a little unusual, so it took me a bit of time to really get it.
But basically, as the documentation states (in the <a href="https://docs.javelit.io/get-started/fundamentals">fundamentals section</a>):</p>
<blockquote>
<p>Javelit&rsquo;s architecture allows you to write apps the same way you write plain Java methods.
To unlock this, Javelit apps have a unique data flow: any time something must be updated on the screen,
Javelit reruns your entire Java main method from top to bottom.</p></blockquote>
<p>So you have to think about it as if there were somehow a big loop around your UI code,
and Javelit redraws it whenever you modify the source code (because of the live-reload capability),
or of course, when a user interacts somehow with the app (submitting a form, clicking a button, moving a slider, etc.)</p>
<p>It&rsquo;s possible to <a href="https://docs.javelit.io/get-started/installation/embedded-vanilla">embed</a> it in your own servers,
but here, I&rsquo;ll illustrate it with the <a href="https://docs.javelit.io/get-started/installation/standalone">standalone command-line tool</a>,
which I&rsquo;ve installed thanks to <a href="https://www.jbang.dev/">Jbang</a>.</p>
<h2 id="it-always-starts-with-_hello-world_">It always starts with <em>&ldquo;Hello World!&rdquo;</em></h2>
<p>A simple example could be:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">/// usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.javelit.core.Jt</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">App</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;Hello World!&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Jt.<span style="color:#4070a0">markdown</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            ## My first official message
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Hello World!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, once Javelit is <a href="https://docs.javelit.io/get-started/installation/standalone#install-javelit">installed</a>, you&rsquo;d run it with the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>javelit run App.java
</span></span></code></pre></div><p>It will open your browser automatically, and you&rsquo;ll be able to view the app UI.
Then, start making some changes to the title or markdown text, and notice how the UI is live reloaded.</p>
<p>Later on, add maybe a button, or the <a href="https://docs.javelit.io/develop/api-reference">many components</a> available,
like the various text elements, the input elements &amp; forms, the containers, pages &amp; layouts,
or the data components like tables or charts with <a href="https://echarts.icepear.org/#/">Apache Echarts</a>.</p>
<blockquote>
<p>Have a look at the more complete <a href="https://docs.javelit.io/get-started/installation/standalone#create-a-hello-world-app-and-run-it">Hello World</a>
from the documentation, which shows some interactivity with a button click counter.</p></blockquote>
<h2 id="creating-an-interactive-image-playground-with-nano-banana">Creating an interactive image playground with Nano Banana</h2>
<p>After &ldquo;Hello World!&rdquo;, you&rsquo;ve got to build something a little more involved, right?</p>
<p>Since I love playing with <strong>Nano Banana</strong> (i.e. Gemini 2.5 Flash Image) to create and edit pictures,
I decided to build an <strong>interactive image playground, to create new images,
and then incrementally edit the image with further prompts</strong>.</p>
<p>Here&rsquo;s the UI I came up with, and let&rsquo;s see how to build and interact with it:</p>
<p><figure>
  <a href="#img-ff7d3f7172139cfd8da4005d30d68e75">
    <img src="/img/nano-banana/javelit-nano-banana-playground.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ff7d3f7172139cfd8da4005d30d68e75">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/javelit-nano-banana-playground.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>What do we see in that UI?
A title, a form containing a text area to enter the prompts, and a button to launch the image generation.
So we&rsquo;ll layout those components in that order:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">App</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;🍌 Nano Banana Playground 🍌&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>form<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">form</span>().<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>text<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textArea</span>(<span style="color:#4070a0">&#34;Image prompt&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">placeholder</span>(<span style="color:#4070a0">&#34;An impressionist painting of a cat&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(form);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(Jt.<span style="color:#4070a0">formSubmitButton</span>(<span style="color:#4070a0">&#34;Generate image&#34;</span>).<span style="color:#4070a0">use</span>(form))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#60a0b0;font-style:italic">// Nano Banana magic to generate or edit the image, then...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>Jt.<span style="color:#4070a0">html</span>(<span style="color:#4070a0">&#34;&lt;img src=&#39;data:&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#4070a0">&#34;;base64,&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>b64encoded<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#39;&gt;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We added:</p>
<ul>
<li>a title with the <code>Jt.title().use()</code> method,</li>
<li>a form, to associate the text area and submit button, with <code>Jt.form().use()</code>,</li>
<li>a text area, with a placeholder, and hosted within the form, with <code>Jt.textArea().use()</code>,</li>
<li>a button to launch the image generation, with <code>Jt.formSubmitButton().use()</code>,</li>
<li>and <code>Jt.html().use()</code> to append an image as a data src <code>img</code> (there&rsquo;s no image component yet, but it&rsquo;s on the <a href="https://github.com/javelit/javelit/discussions/39">roadmap</a>).</li>
</ul>
<p>But what&rsquo;s more interesting is this mysterious <code>if</code> statement&hellip;
The first time, the UI is drawn with the title and form.
But since the user hasn&rsquo;t yet clicked the submit button, the <code>formSubmitButton()</code> method returns <code>false</code>.
So the code inside the <code>if</code> isn&rsquo;t executed.</p>
<p>But once the user interacts with the UI, after having entered some text and clicked on the button,
this time the method will return <code>true</code>, and the image component (here the HTML component) is going to be added to the UI.</p>
<p>At first, this is not really obvious, as you have to think in terms of loop redrawing the UI after each interaction with the components.
But you&rsquo;ll get the hang of it after a little while.</p>
<h2 id="handling-state">Handling state</h2>
<p>The idea of this image playground is to</p>
<ul>
<li>first, create a brand-new image,</li>
<li>but then, you change the prompt with some image editing commands, and each time you submit the form, the image will be updated accordingly.</li>
</ul>
<p>So you need to somehow <a href="https://docs.javelit.io/get-started/fundamentals/advanced-concepts#session-state">keep track of the state</a>
from the previous interaction and rendering loop.
How do you do that? With the <code>Jt.sessionState()</code>.</p>
<p>For example, I want to save the bytes and the mime type of the image generated by Nano Banana, I would do:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;mimeType&#34;</span>,<span style="color:#bbb"> </span>mimeType);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;bytes&#34;</span>,<span style="color:#bbb"> </span>data);<span style="color:#bbb">
</span></span></span></code></pre></div><p>And if I want to get those variables back in the next rendering loop, I&rsquo;d write:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">getString</span>(<span style="color:#4070a0">&#34;mimeType&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>data<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;bytes&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>It&rsquo;s a so-called <em>typed map</em>, and there are many methods like <code>putIfAbsent()</code>, <code>computeInt()</code>, etc.</p>
<h2 id="the-final-version-of-the-code-of-our-playground">The final version of the code of our playground</h2>
<p>Most of the code below is actually generating and editing the image,
as I explained in previous articles <a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">using the Gemini GenAI Java SDK</a> directly,
or <a href="https://glaforge.dev/posts/2025/09/22/creative-ai-agents-with-adk-and-nano-banana/">within ADK for Java</a>.
The UI code from Javelit is really just a dozen lines or so!</p>

<details>
  <summary>Click to view the whole code</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">/// usr/bin/env jbang &#34;$0&#34; &#34;$@&#34; ; exit $?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//DEPS com.google.genai:google-genai:1.24.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">demo</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.Client</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Content</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.GenerateContentConfig</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Part</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.javelit.core.Jt</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.Base64</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">App</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Jt.<span style="color:#4070a0">title</span>(<span style="color:#4070a0">&#34;🍌 Nano Banana Playground 🍌&#34;</span>).<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>form<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">form</span>().<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>imgContainer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">empty</span>().<span style="color:#4070a0">use</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>text<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">textArea</span>(<span style="color:#4070a0">&#34;Image prompt&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">placeholder</span>(<span style="color:#4070a0">&#34;An impressionist painting of a cat&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">use</span>(form);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(Jt.<span style="color:#4070a0">formSubmitButton</span>(<span style="color:#4070a0">&#34;Generate image&#34;</span>).<span style="color:#4070a0">use</span>(form))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>mimeTypeFromState<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">getString</span>(<span style="color:#4070a0">&#34;mimeType&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>bytesFromState<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(<span style="color:#902000">byte</span><span style="color:#666">[]</span>)<span style="color:#bbb"> </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;bytes&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Content<span style="color:#bbb"> </span>content;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// first run --&gt; create a brand-new image</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(mimeTypeFromState<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">||</span><span style="color:#bbb"> </span>bytesFromState<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>content<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">fromParts</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>Part.<span style="color:#4070a0">fromText</span>(text)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// second run --&gt; edit the previously generated image</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>content<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">fromParts</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>Part.<span style="color:#4070a0">fromBytes</span>(bytesFromState,<span style="color:#bbb"> </span>mimeTypeFromState),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>Part.<span style="color:#4070a0">fromText</span>(text)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateContent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;gemini-2.5-flash-image-preview&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>content,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>GenerateContentConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#4070a0">&#34;TEXT&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;IMAGE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>response.<span style="color:#4070a0">candidates</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">flatMap</span>(candidates<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>candidates.<span style="color:#4070a0">getFirst</span>().<span style="color:#4070a0">content</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">flatMap</span>(Content::parts)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">flatMap</span>(parts<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>parts.<span style="color:#4070a0">getLast</span>().<span style="color:#4070a0">inlineData</span>()))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">ifPresent</span>(inlineData<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>String<span style="color:#bbb"> </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>inlineData.<span style="color:#4070a0">mimeType</span>().<span style="color:#4070a0">orElse</span>(<span style="color:#4070a0">&#34;image/png&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>inlineData.<span style="color:#4070a0">data</span>().<span style="color:#4070a0">ifPresent</span>(data<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;mimeType&#34;</span>,<span style="color:#bbb"> </span>mimeType);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Jt.<span style="color:#4070a0">sessionState</span>().<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;bytes&#34;</span>,<span style="color:#bbb"> </span>data);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>String<span style="color:#bbb"> </span>b64encoded<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Base64.<span style="color:#4070a0">getEncoder</span>().<span style="color:#4070a0">encodeToString</span>(data);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Jt.<span style="color:#4070a0">html</span>(<span style="color:#4070a0">&#34;&lt;img src=&#39;data:&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#4070a0">&#34;;base64,&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>b64encoded<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#39;&gt;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">use</span>(imgContainer);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<h2 id="go-ahead-and-have-fun-with-javelit">Go ahead and have fun with Javelit!</h2>
<p>I had a lot of fun playing with <a href="https://javelit.io/">Javelit</a> so far,
and I&rsquo;m looking forward to using this nice little tool to experiment with various application ideas.
I highly encourage you to try it out, so <a href="https://javelit.io/">go check out Javelit</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Building AI Agents with ADK for Java</title><link>https://glaforge.dev/talks/2025/10/22/building-ai-agents-with-adk-for-java/</link><pubDate>Wed, 22 Oct 2025 12:18:14 +0200</pubDate><guid>https://glaforge.dev/talks/2025/10/22/building-ai-agents-with-adk-for-java/</guid><description>&lt;p>At Devoxx Belgium, I recently had the chance to present this new talk dedicated to
&lt;a href="https://google.github.io/adk-docs/">ADK for Java&lt;/a>,
the open source Agent Development Kit framework developed by Google.&lt;/p>
&lt;p>The presentation covered:&lt;/p>
&lt;ul>
&lt;li>an introduction to the notion of AI agents&lt;/li>
&lt;li>how to &lt;strong>get started in a Java and Maven project&lt;/strong>&lt;/li>
&lt;li>how to create your first agent&lt;/li>
&lt;li>how to debug an agent via the &lt;strong>Dev UI&lt;/strong>&lt;/li>
&lt;li>the coverage of the various &lt;strong>tools&lt;/strong> (custom function tools, built-in tools like Google Search or code execution, an agent as tool, MCP tools)&lt;/li>
&lt;li>an overview of the different ways to combine agents into a &lt;strong>multi-agent system&lt;/strong>: sub-agents, sequential agents, parallel agents, loop agents&lt;/li>
&lt;li>some details on the &lt;strong>event loop&lt;/strong> and services (session and state management, artifacts, runner&amp;hellip;)&lt;/li>
&lt;li>&lt;strong>structured&lt;/strong> input / output schemas&lt;/li>
&lt;li>the various &lt;strong>callbacks&lt;/strong> in the agent lifecycle&lt;/li>
&lt;li>the integration with LangChain4j (to give access to the plethora of LLMs supported by LangChain4j)&lt;/li>
&lt;li>the definition of agents via &lt;strong>configuration in YAML&lt;/strong>&lt;/li>
&lt;li>the new &lt;strong>long-term memory&lt;/strong> support&lt;/li>
&lt;li>the &lt;strong>plugin&lt;/strong> system&lt;/li>
&lt;li>the new external &lt;strong>code executors&lt;/strong> (via Docker containers or backed by Google Cloud Vertex AI)&lt;/li>
&lt;li>how to launch an agent with the Dev UI from &lt;strong>JBang&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="slides-of-the-presentation">Slides of the presentation&lt;/h2>
&lt;p>The slide deck of this session is embedded below:&lt;/p></description><content:encoded>
<![CDATA[<p>At Devoxx Belgium, I recently had the chance to present this new talk dedicated to
<a href="https://google.github.io/adk-docs/">ADK for Java</a>,
the open source Agent Development Kit framework developed by Google.</p>
<p>The presentation covered:</p>
<ul>
<li>an introduction to the notion of AI agents</li>
<li>how to <strong>get started in a Java and Maven project</strong></li>
<li>how to create your first agent</li>
<li>how to debug an agent via the <strong>Dev UI</strong></li>
<li>the coverage of the various <strong>tools</strong> (custom function tools, built-in tools like Google Search or code execution, an agent as tool, MCP tools)</li>
<li>an overview of the different ways to combine agents into a <strong>multi-agent system</strong>: sub-agents, sequential agents, parallel agents, loop agents</li>
<li>some details on the <strong>event loop</strong> and services (session and state management, artifacts, runner&hellip;)</li>
<li><strong>structured</strong> input / output schemas</li>
<li>the various <strong>callbacks</strong> in the agent lifecycle</li>
<li>the integration with LangChain4j (to give access to the plethora of LLMs supported by LangChain4j)</li>
<li>the definition of agents via <strong>configuration in YAML</strong></li>
<li>the new <strong>long-term memory</strong> support</li>
<li>the <strong>plugin</strong> system</li>
<li>the new external <strong>code executors</strong> (via Docker containers or backed by Google Cloud Vertex AI)</li>
<li>how to launch an agent with the Dev UI from <strong>JBang</strong></li>
</ul>
<h2 id="slides-of-the-presentation">Slides of the presentation</h2>
<p>The slide deck of this session is embedded below:</p>
<script async class="speakerdeck-embed" data-id="1cd42280a265486396c0822b0e0ec716" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="video-recording-of-the-talk">Video recording of the talk</h2>
<p>And you can also watch the recoding of this presentation here:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/L6V6aQixOZU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="samples-demonstrated-during-the-conference">Samples demonstrated during the conference</h2>
<p>During the presentation, I demonstrated a lot of samples showing ADK in action.
You can find all the <a href="https://github.com/glaforge/ai-agent-protocols/tree/main/adk/src/main/java/agents/adk">examples</a>
in this <a href="https://github.com/glaforge/ai-agent-protocols/">GitHub repository</a>.
There are also a couple of servers implementing an MCP server and an A2A server agent.</p>
<p>Among those examples, you&rsquo;ll find:</p>
<ul>
<li>a simple <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_10_ScienceTeacher.java">science teacher</a> agent</li>
<li>the same <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_12_ScienceTeacher_Live.java">science teacher</a> agent but using voice (with a Gemini Live model)</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_20_StockTicker.java">stock ticker</a> agent that uses a local Java method to look up (fake) stock prices</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_22_LatestNews_Search.java">news search agent</a> configured to use the Google Search built-in tool</li>
<li>an agent that uses the <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_24_PythonCoder.java">built-in Python code executor tool</a></li>
<li>an agent taking advantage of <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_26_LocalGuide_Maps.java">Google Maps grounding</a> to find local landmarks or restaurants</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_28_MoonExpert_MCP.java">moon expert</a>! which calls a remote MCP server</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_30_SearchAndTweet_SubAgents.java">multi-agent</a> using the sub-agents approach to search about some topic, and the potentially craft social post messages</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_32_TripPlanner_Sequential.java">trip planner</a> agent using a sequential agent</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_34_CompanyDetective_Parallel.java">company detective</a> agent using the parallel agent approach to launch different searches in parallel</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_36_CodeRefiner_Loop_Exit.java">code refiner</a> agent using the ADK loop agent to loop over different tasks (writing code &amp; critiquing code)</li>
<li>a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_40_WeatherForecast_Callback.java">weather forecast</a> agent just to show the various callbacks you can configure on an agent</li>
<li>an agent using a <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_50_Coffee_LangChain4j.java">local agent running in Ollama</a>, thanks to <a href="https://developers.googleblog.com/en/adk-for-java-opening-up-to-third-party-language-models-via-langchain4j-integration/">my ADK / LangChain4j bridge</a></li>
<li>an example showing how to <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_60_CapitalCity_YAML.java">load a YAML-defined agent</a></li>
<li>and the bonus for the end: an <a href="https://github.com/glaforge/ai-agent-protocols/blob/main/adk/src/main/java/agents/adk/_70_ImageEditor_NanoBanana.java">agent using the Nano Banana</a> image generation and edition model</li>
</ul>
<h2 id="updated-adk-template-project">Updated ADK template project</h2>
<p>With the recent (somewhat silent) release of <a href="https://github.com/google/adk-java/releases/tag/v0.3.0">version 0.3.0</a>,
I seized the opportunity to also update my <a href="https://github.com/glaforge/adk-java-maven-template">ADK template project</a> on GitHub.
In a <a href="https://glaforge.dev/posts/2025/05/27/adk-java-github-template/">recent article</a>, I wrote about this template project,
how you can clone it or reuse it, to get started with ADK for Java easily, with a sample agent, and a Maven build.</p>
<h2 id="getting-started-with-a-codelab">Getting started with a codelab</h2>
<p>Additionally to the GitHub <a href="https://github.com/glaforge/adk-java-maven-template">project template</a>,
I have developed a <a href="https://goo.gle/codelab-adk-java">codelab to build AI agents with ADK for Java</a>, for Java developers.
This codelab goes through setting up your environment, writing your first agent, empowering an agent with tools, using the built-in Google Search tool,
mastering the various types of workflow patterns (sub-agent, sequential, parallel, loop).</p>
<p><a href="https://goo.gle/codelab-adk-java"><figure>
  <a href="#img-651ae890ec97b2b9c4c4b09b67ab0a7d">
    <img src="/img/adk/adk-java-codelab-screenshot.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-651ae890ec97b2b9c4c4b09b67ab0a7d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/adk-java-codelab-screenshot.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Creative Java AI agents with ADK and Nano Banana 🍌</title><link>https://glaforge.dev/posts/2025/09/22/creative-ai-agents-with-adk-and-nano-banana/</link><pubDate>Mon, 22 Sep 2025 16:51:37 +0200</pubDate><guid>https://glaforge.dev/posts/2025/09/22/creative-ai-agents-with-adk-and-nano-banana/</guid><description>&lt;p>Large Language Models (LLMs) are all becoming &lt;em>&amp;ldquo;multimodal&amp;rdquo;&lt;/em>.
They can process text, but also other &lt;em>&amp;ldquo;modalities&amp;rdquo;&lt;/em> in input, like pictures, videos, or audio files.
But models that output more than just text are less common&amp;hellip;&lt;/p>
&lt;p>Recently, I wrote about my &lt;a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">experiments with &lt;strong>Nano Banana&lt;/strong>&lt;/a> &amp;#x1f34c; (in Java),
a &lt;strong>Gemini chat model flavor that can create and edit images&lt;/strong>.
This is pretty handy in particular for interactive creative tasks, like for example a marketing assistant that would help you design a new product,
by describing it, by futher tweaking its look, by exposing it in different settings for marketing ads, etc.&lt;/p></description><content:encoded>
<![CDATA[<p>Large Language Models (LLMs) are all becoming <em>&ldquo;multimodal&rdquo;</em>.
They can process text, but also other <em>&ldquo;modalities&rdquo;</em> in input, like pictures, videos, or audio files.
But models that output more than just text are less common&hellip;</p>
<p>Recently, I wrote about my <a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">experiments with <strong>Nano Banana</strong></a> &#x1f34c; (in Java),
a <strong>Gemini chat model flavor that can create and edit images</strong>.
This is pretty handy in particular for interactive creative tasks, like for example a marketing assistant that would help you design a new product,
by describing it, by futher tweaking its look, by exposing it in different settings for marketing ads, etc.</p>
<p>The <em>&ldquo;Nano Banana&rdquo;</em> &#x1f34c; model we&rsquo;ll use today is the nickname for the popular <code>gemini-2.5-flash-image-preview</code> model.
It&rsquo;s not just a <em>conversational</em> AI; it&rsquo;s a creative partner that can generate and edit images right within a chat session.
Not only it generates text, but also images.</p>
<p>And today, we&rsquo;re going to explore how to configure and use this model inside an AI agent developed with
<a href="https://google.github.io/adk-docs/">ADK</a> (<strong>Agent Development Kit</strong>, and especially <a href="https://github.com/google/adk-java">its Java version</a>).
We&rsquo;ll focus on a key piece of the puzzle: processing the image output from the model and saving it for later use.</p>
<h2 id="meet-the-nanobananacreativeagent">Meet the <code>NanoBananaCreativeAgent</code></h2>
<p>Let&rsquo;s look at some code. Our example is the following <code>NanoBananaCreativeAgent</code> class.
Its goal is to act as a creative assistant, using the &#x1f34c; <em>&ldquo;Nano Banana&rdquo;</em> model to handle image-related tasks based on user prompts.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Remark</span>
      </div>
      <div class="admonition-content">
        <p>Maybe in a later post, we&rsquo;ll see how to create a more complete agent like the marketing scenario I suggested in introduction,
but for now, I want to highlight <strong>how to configure the model and save its output</strong> for later use.</p>
      </div>
    </div><p>Here’s how we define the agent using ADK&rsquo;s <code>LlmAgent.builder()</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.agents.BaseAgent</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.agents.LlmAgent</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Content</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Part</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.reactivex.rxjava3.core.Maybe</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.List</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">NanoBananaCreativeAgent</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>BaseAgent<span style="color:#bbb"> </span><span style="color:#06287e">getAgent</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;nano-banana-creative-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash-image-preview&#34;</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// 🍌</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                You are a creative assistant, and you help users
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                create new images or edit existing ones, using the
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                Nano Banana model (aka Gemini 2.5 Flash Image)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">afterModelCallback</span>((callbackContext,<span style="color:#bbb"> </span>llmResponse)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#60a0b0;font-style:italic">// We&#39;ll zoom in on this part next!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... main method to run the agent and other helpers</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>There are two important lines here for enabling image generation:</p>
<ol>
<li><code>.model(&quot;gemini-2.5-flash-image-preview&quot;)</code>:
This tells the ADK to route requests to the specific model endpoint capable of generating and editing images.</li>
<li><code>.instruction(...)</code>:
The instruction primes the model, letting it know its role is to be a creative assistant focused on image tasks.</li>
</ol>
<h2 id="handling-the-image-response-with-a-callback">Handling the image response with a callback</h2>
<p>When you ask the model to <em>&ldquo;create an image of a cat wearing a party hat,&rdquo;</em> it doesn&rsquo;t return a URL or a file path.
It returns the image data directly in its response, typically as a <code>Part</code> containing binary data, in a big byte array.</p>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>The model usually replies with some text to introduce the image, along with the image.
But sometimes, it can also return text-only, in particular when it asks for clarifications for generating the requested image.
So it&rsquo;s important to check that an image is indeed present in the output of the model.
Also, when the output contains an image, there&rsquo;s only a single one.
It never generates more than one — which means I could use a <code>findFirst()</code> instead of a <code>forEach()</code> in the implementation below.</p>
      </div>
    </div><p>How do we capture and use this binary data?
This is where ADK&rsquo;s <code>afterModelCallback</code> becomes handy.
It&rsquo;s a <strong>hook</strong> that lets you execute custom Java code <strong>immediately after the LLM sends its response</strong>, but before the agent&rsquo;s turn is finished.</p>
<p>Let&rsquo;s look at our callback code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>.<span style="color:#4070a0">afterModelCallback</span>((callbackContext,<span style="color:#bbb"> </span>llmResponse)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>llmResponse.<span style="color:#4070a0">content</span>()<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// 1. Let&#39;s find the image part!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">flatMap</span>(Content::parts)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">flatMap</span>(List::stream)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Filter parts containing image content</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">filter</span>(part<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>part.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">isPresent</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">forEach</span>(part<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// 2. Save the image as an artifact for the pipeline</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>callbackContext.<span style="color:#4070a0">saveArtifact</span>(<span style="color:#4070a0">&#34;rendered-image&#34;</span>,<span style="color:#bbb"> </span>part);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// 3. Potentially save the image as a file elsewhere</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Blob<span style="color:#bbb"> </span>blob<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>part.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>imageBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>blob.<span style="color:#4070a0">data</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>String<span style="color:#bbb"> </span>mimeType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>blob.<span style="color:#4070a0">mimeType</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Returning empty means not altering the agent&#39;s response</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">empty</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>})<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s break down what&rsquo;s happening in this lambda:</p>
<ol>
<li>
<p><strong>Find the image part</strong>: We use a Java Stream to navigate the <code>llmResponse</code> structure.
A response can have zero or one <code>Content</code> object (an <code>Optional</code>), with an optional list of <code>Part</code>s (which can be <em>text</em>, <em>function calls</em>, or <em>inline data</em>).
We filter down to find the <code>Part</code> that contains <code>inlineData</code> — this is our image.</p>
</li>
<li>
<p><strong>Save as an artifact</strong>: <code>callbackContext.saveArtifact(&quot;rendered-image&quot;, part)</code> is a key ADK feature.
It saves the raw image <code>Part</code> into the agent&rsquo;s artifact registry under the name <code>rendered-image</code>.
This makes the generated image available to other agents or tools that might run later in a more complex pipeline.</p>
</li>
<li>
<p><strong>Do something with the image bytes</strong>: Potentially, instead of (or in addition to) saving the image as an artifact,
you can decide to do something yourself with the bytes of the image and its MIME type,
like saving that file directly to the file system if you&rsquo;re building some kind of command-line based agent tool.</p>
</li>
</ol>
<h2 id="going-further-building-a-creative-marketing-agent">Going further: building a creative marketing agent</h2>
<p>This simple agent is the building block for a more complex creative workflow.
Imagine a <em>&ldquo;Creative Marketing Agent&rdquo;</em> built as a pipeline of agents.</p>
<ul>
<li>
<p><strong>Step 1 — Product ideation.</strong> A user interacts with our <code>NanoBananaCreativeAgent</code>.
They prompt: <em>&ldquo;Generate an image of a new energy drink can called &lsquo;Cosmic Charge&rsquo;. It should be dark blue with a glowing yellow lightning bolt.&rdquo;</em></p>
</li>
<li>
<p><strong>Step 2 — Image generation &amp; persistence.</strong> Our agent calls the model to generate the image.
The user iterates potentially a few rounds to further improve the rendered picture, thanks to Nano Banana&rsquo;s editing capabilities.
The <code>afterModelCallback</code> we just analyzed fires, and <code>cosmic-charge.png</code> is saved to the file system,
and/or the image is also saved as an artifact in the agent&rsquo;s session.</p>
</li>
<li>
<p><strong>Step 3 — Further asset generation.</strong> A second agent in the pipeline
(via an ADK <code>SequentialAgent</code> that we <a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">explored in a previous article</a>), a <code>MarketingAssetAgent</code>, is triggered.
Its instruction might be: <em>&ldquo;You will be given a product image.
Your job is to create a marketing banner for social media.&rdquo;</em>
This agent can now be given a new prompt like, <em>&ldquo;Take the product image from the &lsquo;rendered-image&rsquo; artifact and place it on a background of a starry nebula.
Add the text &lsquo;Feel the Power of the Cosmos!&rsquo;&rdquo;.</em>
Or you could also have a dedicated video generation agent,
using the <a href="https://gemini.google/overview/video-generation/">Veo 3</a> video model,
to generate a video illustrating the product in action, like someone drinking this fancy energy drink.</p>
</li>
</ul>
<p>By saving the image to a file or as artifact, thanks to the callback trick, we&rsquo;ve successfully passed a complex, generated asset from one stage of our agentic workflow to the next.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Integrating advanced image generation and editing into your Java agentic applications is no longer science-fiction.
With the <strong>Agent Development Kit</strong> for Java, with just a few lines of hook code,
you can configure an agent to use powerful multimodal models like <strong>Nano Banana</strong>.
By leveraging the <code>afterModelCallback</code> in your ADK agent definition, you gain precise control over the model&rsquo;s output,
allowing you to process, save, and chain creative tasks together to build more useful and creative agents.</p>
<p>So go ahead, start experimenting, and see what amazing creative workflows you can build!
And, of course, be sure to read my series of <a href="https://glaforge.dev/tags/agent-development-kit/">articles on ADK for Java</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Creating a Streamable HTTP MCP server with Micronaut</title><link>https://glaforge.dev/posts/2025/09/16/creating-a-streamable-http-mcp-server-with-micronaut/</link><pubDate>Tue, 16 Sep 2025 10:00:31 +0200</pubDate><guid>https://glaforge.dev/posts/2025/09/16/creating-a-streamable-http-mcp-server-with-micronaut/</guid><description>&lt;p>In previous articles, I explored how to
&lt;a href="https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/">create an MCP server with Micronaut&lt;/a>
by &lt;em>vibe-coding&lt;/em> one, following the
&lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol specification&lt;/a>
(which was a great way to better understand the underpinnings) and how to
&lt;a href="https://glaforge.dev/posts/2025/06/09/building-an-mcp-server-with-quarkus-and-deploying-on-google-cloud-run/">create an MCP server with Quarkus&lt;/a>.&lt;/p>
&lt;p>Micronaut lacked a dedicated module for creating MCP servers, but fortunately, recently Micronaut added official support for MCP,
so I was eager to try it out!&lt;/p>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition note">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">&lt;path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/>&lt;/svg>
&lt;span>For the impatient&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>You can &lt;a href="https://github.com/glaforge/mn-mcp-server">checkout the code&lt;/a> we&amp;rsquo;ll be covering in this article on GitHub.&lt;/p></description><content:encoded>
<![CDATA[<p>In previous articles, I explored how to
<a href="https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/">create an MCP server with Micronaut</a>
by <em>vibe-coding</em> one, following the
<a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol specification</a>
(which was a great way to better understand the underpinnings) and how to
<a href="https://glaforge.dev/posts/2025/06/09/building-an-mcp-server-with-quarkus-and-deploying-on-google-cloud-run/">create an MCP server with Quarkus</a>.</p>
<p>Micronaut lacked a dedicated module for creating MCP servers, but fortunately, recently Micronaut added official support for MCP,
so I was eager to try it out!</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>For the impatient</span>
      </div>
      <div class="admonition-content">
        <p>You can <a href="https://github.com/glaforge/mn-mcp-server">checkout the code</a> we&rsquo;ll be covering in this article on GitHub.</p>
      </div>
    </div><h2 id="what-to-build">What to build?</h2>
<p>Like in my previous article with Quarkus, I decided to build another version of my &#x1f314; moon phases MCP server.
This is interesting to be able to contrast Quarkus and Micronaut&rsquo;s approaches.</p>
<p>I reused my code for calculating the moon phases.
My <code>MoonPhasesService</code> is fairly simple (as long as you don&rsquo;t look at the exact math calculation)
and consists in two methods:</p>
<ul>
<li><code>currentMoonPhase()</code> — to know the phase at this point in time,</li>
<li><code>moonPhaseAtUnixTimestamp(long timeSeconds)</code> — to know the phase at a specific point in time.</li>
</ul>
<p>The contract is as follows, nothing specific to MCP for now:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Singleton</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MoonPhasesService</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span><span style="color:#06287e">currentMoonPhase</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">/*...*/</span><span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span><span style="color:#06287e">moonPhaseAtUnixTimestamp</span>(<span style="color:#902000">long</span><span style="color:#bbb"> </span>timeSeconds)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">/*...*/</span><span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Compared to my Quarkus version, the service returns <code>MoonPhase</code> <code>record</code>s instead of <code>enum</code> values,
as it seems Micronaut is unhappy with returning my <code>enum</code>.
So I changed <code>MoonPhase</code> to look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Java" data-lang="Java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@JsonSchema</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Introspected</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">MoonPhase</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@NotBlank</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>phase,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@NotBlank</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>emoji<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>What&rsquo;s interesting here is the <code>@JsonSchema</code> annotation which comes from the Micronaut JSON Schema module,
which provides very rich support for all the subtleties of the JSON Schema specification.
The <code>@Instrospected</code> annotation is here to help with annotation processing and Ahead-of-Time compilation.</p>
<p>Let&rsquo;s look at the <code>MoonPhasesMcpServer</code> now:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Singleton</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MoonPhasesMcpServer</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Inject</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>MoonPhasesService<span style="color:#bbb"> </span>moonPhasesService;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;current-moon-phase&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Provides the current moon phase&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span><span style="color:#06287e">currentMoonPhase</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>moonPhasesService.<span style="color:#4070a0">currentMoonPhase</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;moon-phase-at-date&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Provides the moon phase at a certain date (with a format of yyyy-MM-dd)&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span><span style="color:#06287e">moonPhaseAtDate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@ToolArg</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;localDate&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@NotBlank</span><span style="color:#bbb"> </span><span style="color:#555;font-weight:bold">@Pattern</span>(regexp<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\\d{4}-\\d{2}-\\d{2}&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>localDate<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>LocalDate<span style="color:#bbb"> </span>parsedLocalDate<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LocalDate.<span style="color:#4070a0">parse</span>(localDate);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>moonPhasesService.<span style="color:#4070a0">moonPhaseAtUnixTimestamp</span>(parsedLocalDate.<span style="color:#4070a0">toEpochDay</span>()<span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>86400);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You&rsquo;ll find the same couple annotations as in Quarkus: <code>@Tool</code> and <code>@ToolArg</code>.
In Micronaut, <code>@ToolArg</code> is missing a <code>description</code> field, but it should be added soon.</p>
<p>What&rsquo;s more powerful here in Micronaut is the use of Micronaut Validation annotations:
notice the <code>@NotBlank</code> and even better, the <code>@Pattern</code> annotation!</p>
<p>With Micronaut, I don&rsquo;t have to handle the mal-formed inputs, as they are caught by validation much earlier.
If the input is incorrect, Micronaut will handle the situation on its own, and your method won&rsquo;t even be called.
So no need to handle the bad values.</p>
<h2 id="testing-the-mcp-server-with-the-mcp-inspector">Testing the MCP server with the MCP Inspector</h2>
<p>When using MCP Inspector to test my server manually, if I pass a blank value to the <code>moon-phase-at-date</code> method,
I&rsquo;ll see validation kicking in:</p>
<p><figure>
  <a href="#img-9286fc895bcacb87c962507b577f2d42">
    <img src="/img/mcp/mn-mcp-input-error.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9286fc895bcacb87c962507b577f2d42">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/mn-mcp-input-error.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<pre tabindex="0"><code>MCP error -32603: moonPhaseAtDate.localDate: must not be blank,
moonPhaseAtDate.localDate: must match &#34;\d{4}-\d{2}-\d{2}&#34;
</code></pre><p>Extra bonus point: Micronaut MCP will create (at compile time) the JSON schemas for the various <code>@JsonSchema</code> annotated beans,
adding more fine-grained information about the manipulated input / output structures.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;$schema&#34;</span>: <span style="color:#4070a0">&#34;https://json-schema.org/draft/2020-12/schema&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;$id&#34;</span>: <span style="color:#4070a0">&#34;http://localhost:8080/schemas/moonPhase.schema.json&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;Phase of the moon&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;The phase of the moon is composed of the name of the phase and an emoji representing it&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;object&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;properties&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;emoji&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;string&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;minLength&#34;</span>: <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;phase&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;string&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;minLength&#34;</span>: <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For those schemas to be served as static assets, <code>application.properties</code> must configure the static resources:</p>
<pre tabindex="0"><code># specify the HTTP Streamable transport
micronaut.mcp.server.transport=HTTP
micronaut.mcp.server.info.name=moon-phases
micronaut.mcp.server.info.version=1.0.0

# Specify how &amp; where the schemas should be exposed
micronaut.router.static-resources.jsonschema.paths=classpath:META-INF/schemas
micronaut.router.static-resources.jsonschema.mapping=/schemas/**

# Potentially define a specific base URL, otherwise it&#39;s infered
# micronaut.jsonschema.validation.baseUri=https://example.com/schemas
</code></pre><h2 id="quick-look-at-the-dependencies">Quick look at the dependencies</h2>
<p>You can checkout the <a href="https://github.com/glaforge/mn-mcp-server">code</a>
and read the <a href="https://github.com/glaforge/mn-mcp-server/blob/main/README.md">README</a>,
but I&rsquo;d like to mention how I scaffolded the project in the first place,
and which dependencies (and tweaks) were needed.</p>
<h3 id="creating-the-micronaut-application">Creating the Micronaut application</h3>
<p>This project was bootstrapped with the <code>mn</code> Micronaut command-line tool,
which can be <a href="https://sdkman.io/sdks/micronaut/">installed via SDKman</a>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mn create-app --build<span style="color:#666">=</span>gradle --jdk<span style="color:#666">=</span><span style="color:#40a070">21</span> --lang<span style="color:#666">=</span>java --test<span style="color:#666">=</span>junit <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>  --features<span style="color:#666">=</span>jackson-databind,json-schema,validation,json-schema-validation mn.mcp.server.mn-mcp-server
</span></span></code></pre></div><p>As the MCP support is based on the official MCP SDK, which is currently tied to Jackson, you must use the Jackson data binding
(not Micronaut&rsquo;s built-in serialization). You need to add <code>json-schema</code>, <code>validation</code>, and <code>json-schema-validation</code> features.</p>
<p>But you&rsquo;ll have to make some tweaks to the dependencies.</p>
<h3 id="custom-dependency-tweaks">Custom dependency tweaks</h3>
<p>The following dependencies were added to <code>build.gradle</code>, or updated, to support the MCP server and enhance JSON Schema generation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// Existing dependencies
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#60a0b0;font-style:italic">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// The Micronaut MCP support
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    implementation<span style="color:#666">(</span><span style="color:#4070a0">&#34;io.micronaut.mcp:micronaut-mcp-server-java-sdk:0.0.3&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// For rich JSON schema handling
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    annotationProcessor<span style="color:#666">(</span><span style="color:#4070a0">&#34;io.micronaut.jsonschema:micronaut-json-schema-processor:1.7.0&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    implementation<span style="color:#666">(</span><span style="color:#4070a0">&#34;io.micronaut.jsonschema:micronaut-json-schema-annotations:1.7.0&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>First of all, the MCP module is not yet part of the <em>features</em> you can select from the <code>mn</code> command,
or from the <a href="https://micronaut.io/launch/">Micronaut launch site</a>,
but once the MCP support stabilizes, it&rsquo;ll be available.</p>
<p>I had to update the dependency version of the JSON Schema support (instead of using the default version from the BOM),
but this new version will be available soon in the Micronaut BOM.</p>
<p>So maybe when you read this, you&rsquo;ll just add the <code>mcp</code> feature to the list of features, and have everything configured properly.
But those tweaks are just because I&rsquo;m living on the bleeding edge right now!</p>
<h2 id="invoking-the-server-via-gemini-cli">Invoking the server via Gemini CLI</h2>
<p>For the fun, I decided to add this MCP server to my <a href="https://google-gemini.github.io/gemini-cli/">Gemini CLI</a> installation.</p>
<p>Before launching the CLI, I installed the MCP server as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gemini mcp add moonPhases --transport http http://localhost:8080/mcp
</span></span></code></pre></div><p>Then when I launch <code>gemini</code> and list the MCP servers, I can see the moon phase server:</p>
<p><figure>
  <a href="#img-4d3845c5070fb0d285816995c9047e8e">
    <img src="/img/mcp/moon-phase-gemini-cli-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-4d3845c5070fb0d285816995c9047e8e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/moon-phase-gemini-cli-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I ask what was the phase of the moon when mankind first landed on the moon
(and Gemini figures out the correct date format, although I gave it in plain English).
Gemini CLI asks for my acknowledgement to execute the MCP server tool:</p>
<p><figure>
  <a href="#img-96714957a36b740f77cb4b38ebb4f021">
    <img src="/img/mcp/moon-phase-gemini-cli-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-96714957a36b740f77cb4b38ebb4f021">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/moon-phase-gemini-cli-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Finally, Gemini CLI responds with a proper English response:</p>
<p><figure>
  <a href="#img-8f6218895a852833c248eb6291aa814b">
    <img src="/img/mcp/moon-phase-gemini-cli-3.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8f6218895a852833c248eb6291aa814b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/mcp/moon-phase-gemini-cli-3.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="going-further">Going further</h2>
<p>I hope you enjoyed the ride so far, but what are the next steps?</p>
<ul>
<li>Check out the <a href="https://github.com/glaforge/mn-mcp-server">source code</a> of this simple HTTP Streamable MCP server.</li>
<li>Read about the <a href="https://micronaut-projects.github.io/micronaut-mcp/latest/guide/#introduction">MCP support in Micronaut</a>.</li>
<li>Have a look at Sergio Delamo&rsquo;s <a href="https://github.com/sdelamo/micronaut-mcp-tools-weather">sample MCP project</a>.</li>
</ul>
<p>Java developers have some great options nowadays for developing their MCP servers, including Quarkus and Micronaut.
Be sure to evaluate those options for your next projects!
For enterprise deployments, nothing beats Java! &#x1f609;
And Micronaut offers a pretty elegant handling of structured inputs and outputs thanks to its rich JSON Schema support.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Generating videos in Java with Veo 3</title><link>https://glaforge.dev/posts/2025/09/10/generating-videos-in-java-with-veo3/</link><pubDate>Wed, 10 Sep 2025 09:46:21 +0200</pubDate><guid>https://glaforge.dev/posts/2025/09/10/generating-videos-in-java-with-veo3/</guid><description>&lt;p>Yesterday, we went bananas &amp;#x1f34c;
&lt;a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">creating and editing images with Nano Banana, in Java&lt;/a>.
Now, what about &lt;strong>generating videos&lt;/strong> as well, still &lt;strong>in Java&lt;/strong>, with &lt;a href="https://deepmind.google/models/veo/">Veo 3&lt;/a>?&lt;/p>
&lt;p>Especially since this week, Google announced that
&lt;a href="https://developers.googleblog.com/en/veo-3-and-veo-3-fast-new-pricing-new-configurations-and-better-resolution/?utm_campaign=CDR_0x7a40493f_default_b444157129&amp;amp;utm_medium=external&amp;amp;utm_source=blog">Veo 3 became generally available&lt;/a>,
with &lt;strong>reduced pricing&lt;/strong>, a &lt;strong>new 9:16 aspect ratio&lt;/strong> (nice for those vertical viral videos) and even with &lt;strong>resolution up to 1080p&lt;/strong>!&lt;/p>
&lt;p>In today&amp;rsquo;s article, we&amp;rsquo;ll see how to create videos, in Java, with the &lt;a href="https://github.com/googleapis/java-genai">GenAI Java SDK&lt;/a>.
We&amp;rsquo;ll create videos either:&lt;/p></description><content:encoded>
<![CDATA[<p>Yesterday, we went bananas &#x1f34c;
<a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">creating and editing images with Nano Banana, in Java</a>.
Now, what about <strong>generating videos</strong> as well, still <strong>in Java</strong>, with <a href="https://deepmind.google/models/veo/">Veo 3</a>?</p>
<p>Especially since this week, Google announced that
<a href="https://developers.googleblog.com/en/veo-3-and-veo-3-fast-new-pricing-new-configurations-and-better-resolution/?utm_campaign=CDR_0x7a40493f_default_b444157129&amp;utm_medium=external&amp;utm_source=blog">Veo 3 became generally available</a>,
with <strong>reduced pricing</strong>, a <strong>new 9:16 aspect ratio</strong> (nice for those vertical viral videos) and even with <strong>resolution up to 1080p</strong>!</p>
<p>In today&rsquo;s article, we&rsquo;ll see how to create videos, in Java, with the <a href="https://github.com/googleapis/java-genai">GenAI Java SDK</a>.
We&rsquo;ll create videos either:</p>
<ul>
<li>with a prompt,</li>
<li>or starting with an existing image.</li>
</ul>
<h2 id="setting-up-your-project">Setting up your project</h2>
<p>Depending on your build tool of choice, you&rsquo;ll have to declare the following dependency for the GenAI SDK:</p>
<h3 id="for-maven">For Maven</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.genai<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-genai<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.15.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><h3 id="for-gradle">For Gradle</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    implementation <span style="color:#4070a0">&#34;com.google.genai:google-genai:1.15.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Next, you&rsquo;ll need to decide if you wish to use a <a href="https://aistudio.google.com/app/apikey">Google AI API key</a>,
or if you have an existing Google Cloud project, and use it for authentication.</p>
<h3 id="using-a-google-ai-api-key">Using a Google AI API key</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="using-a-google-cloud-project">Using a Google Cloud project</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CLOUD_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CLOUD_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">vertexAI</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now we&rsquo;re ready to go!</p>
<h2 id="choosing-the-right-model">Choosing the right model</h2>
<p>There are two <a href="https://ai.google.dev/gemini-api/docs/video?example=dialogue#model-versions&amp;utm_campaign=CDR_0x7a40493f_default_b444157129&amp;utm_medium=external&amp;utm_source=blog">Veo 3 versions</a>, a super fast one, and a highest quality one:</p>
<ul>
<li><code>veo-3.0-generate-001</code></li>
<li><code>veo-3.0-fast-generate-001</code></li>
</ul>
<p>Let&rsquo;s store the model in a variable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>modelName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;veo-3.0-generate-001&#34;</span>;<span style="color:#bbb">
</span></span></span></code></pre></div>
            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>For the rest of this article, I&rsquo;ll use the best and greatest version!
The generated videos are of higher quality, but they are also more expensive.
So you might want to use the <em>&ldquo;fast&rdquo;</em> variant, if you don&rsquo;t need the highest quality possible, and you also want faster video generation.</p>
      </div>
    </div><p>You can create a video in two ways: from a text prompt, where Veo creates the entire scene, or from an existing image that serves as a starting point, preserving its atmosphere and tone.
Let&rsquo;s see both approaches.</p>
<h2 id="generating-a-video-with-a-prompt">Generating a video with a prompt</h2>
<p>The <code>client.models.generateVideos()</code> method we will use has a few overloads.
I usually prefer the one that accepts a <code>GenerateVideosSource</code> builder object.
On that object, you can configure whether you want to pass a prompt, or an image as starting point.</p>
<p>So let&rsquo;s create a video of a funky banana:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>operation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateVideos</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>modelName,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateVideosSource.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">prompt</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            A funky banana is dancing on the dance floor in a
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            nightclub with flashy color lights and faceted ball,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            and in the background, a neon light says &#34;Veo 3&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateVideosConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">aspectRatio</span>(<span style="color:#4070a0">&#34;16:9&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">resolution</span>(<span style="color:#4070a0">&#34;720p&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">generateAudio</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>What does it look (and sound!) like?</p>
<figure class="video">
<video controls preload="metadata" width="800"   >
<source src="/img/veo/video-16-9.mp4" >
</video>
</figure>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Vertex AI and Google AI don&rsquo;t necessarily support the same video configuration options.
The example here was made with Veo from Vertex AI, but with the developer endpoint, for example, you can&rsquo;t specify the resolution or the audio generation parameter.
So be sure to double check which parameters work with the flavor of Veo you&rsquo;re using.</p>
      </div>
    </div><h2 id="generating-a-video-with-an-existing-image">Generating a video with an existing image</h2>
<p>What if instead, I already created an image that I want to animate.
For example, I created this nice dancing banana with Nano Banana
(check yesterday&rsquo;s post on <a href="https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/">Nano Banana from Java</a>):</p>
<p><figure>
  <a href="#img-a40e8a878a20eaf393c08da8e0c9ae89">
    <img src="/img/veo/banana.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a40e8a878a20eaf393c08da8e0c9ae89">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/veo/banana.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>So let&rsquo;s animate <em>that</em> particular image (<code>banana.png</code>) and this time, I&rsquo;ll use a 9:16 vertical format, so that I can easily share it on social media, with a nice aspect ratio for mobile devices:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>operation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateVideos</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>modelName,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateVideosSource.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">prompt</span>(<span style="color:#4070a0">&#34;The banana is dancing to the sound of disco music&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">image</span>(Image.<span style="color:#4070a0">fromFile</span>(<span style="color:#4070a0">&#34;banana.png&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateVideosConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">aspectRatio</span>(<span style="color:#4070a0">&#34;9:16&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">resolution</span>(<span style="color:#4070a0">&#34;1080p&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">generateAudio</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here&rsquo;s the resulting video:</p>
<figure class="video">
<video controls preload="metadata" width="600"   >
<source src="/img/veo/video-banana-9-16.mp4" >
</video>
</figure>
<p>Do you feel like dancing now? Or maybe you&rsquo;d rather eat a banana?</p>
<h2 id="waiting-for-the-video-to-be-generated">Waiting for the video to be generated</h2>
<p>I showed the videos in the previous sections already, but I didn&rsquo;t yet explain how to retrieve those generated videos.</p>
<p>Whereas images are quite fast to generate (so we can wait synchronously for them) videos on the other hand take longer to generate (between 1 and 2 minutes).
That&rsquo;s why we&rsquo;re going to have to <em>poll</em> to check their status.</p>
<p>The <code>generateVideos()</code> method returns a <code>GenerateVideosOperation</code> object, which is a snapshot of the status of the operation.
We have to request a new <code>GenerateVideosOperation</code> instance each time we want to check the status:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(<span style="color:#666">!</span>operation.<span style="color:#4070a0">done</span>().<span style="color:#4070a0">orElse</span>(<span style="color:#007020;font-weight:bold">false</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Thread.<span style="color:#4070a0">sleep</span>(1000);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>operation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">operations</span>.<span style="color:#4070a0">getVideosOperation</span>(operation,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>With a <code>while</code> loop, I&rsquo;m checking every second when/if the video is finished or not.
And I request a new operation object and check again.</p>
<p>When the operation is actually <em>done</em>, we can fetch the video bytes and save them in a file.
There&rsquo;s a bit of Java stream magic involved here, because the API is (unfortunately) relying too much on <code>Optional</code>s for everything:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>operation.<span style="color:#4070a0">response</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">flatMap</span>(GenerateVideosResponse::generatedVideos)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">flatMap</span>(List::stream)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">findFirst</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">ifPresent</span>(video<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">files</span>.<span style="color:#4070a0">download</span>(video,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;video.mp4&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>And here you go, the video is now available and saved in the <code>video.mp4</code> file.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this article, we&rsquo;ve seen <strong>how to use the GenAI Java SDK to generate videos with Veo 3 in Java</strong>.
We explored creating videos from both text prompts and existing images, configuring aspect ratios and resolutions,
and handling the asynchronous nature of video generation by polling for results. With <strong>just a few lines of Java code</strong>,
you can now integrate powerful video generation capabilities into your applications.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Generating and editing images with Nano Banana in Java</title><link>https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/</link><pubDate>Tue, 09 Sep 2025 07:20:21 +0200</pubDate><guid>https://glaforge.dev/posts/2025/09/09/calling-nano-banana-from-java/</guid><description>&lt;p>By now, you&amp;rsquo;ve all probably seen the incredible images generated by the &lt;a href="https://blog.google/products/gemini/updated-image-editing-model/">&lt;strong>Nano Banana&lt;/strong>&lt;/a> model
(also known as Gemini 2.5 Flash Image &lt;em>preview&lt;/em>)?
If you haven&amp;rsquo;t, I encourage you to play with it within &lt;a href="https://aistudio.google.com/prompts/new_chat?model=gemini-2.5-flash-image-preview">Google AI Studio&lt;/a>,
and from the &lt;a href="https://gemini.google.com/">Gemini app&lt;/a>.
or have a look at the &lt;a href="https://x.com/NanoBanana/with_replies">@NanoBanana X/Twitter account&lt;/a> which shares some of its greatest creations.&lt;/p>
&lt;p>&lt;strong>As a Java developer&lt;/strong>, you may be wondering how you can &lt;strong>integrate Nano Banana&lt;/strong> in your &lt;em>own&lt;/em> LLM-powered apps.
This is what this article is about! I&amp;rsquo;ll show you how you can use this model to:&lt;/p></description><content:encoded>
<![CDATA[<p>By now, you&rsquo;ve all probably seen the incredible images generated by the <a href="https://blog.google/products/gemini/updated-image-editing-model/"><strong>Nano Banana</strong></a> model
(also known as Gemini 2.5 Flash Image <em>preview</em>)?
If you haven&rsquo;t, I encourage you to play with it within <a href="https://aistudio.google.com/prompts/new_chat?model=gemini-2.5-flash-image-preview">Google AI Studio</a>,
and from the <a href="https://gemini.google.com/">Gemini app</a>.
or have a look at the <a href="https://x.com/NanoBanana/with_replies">@NanoBanana X/Twitter account</a> which shares some of its greatest creations.</p>
<p><strong>As a Java developer</strong>, you may be wondering how you can <strong>integrate Nano Banana</strong> in your <em>own</em> LLM-powered apps.
This is what this article is about! I&rsquo;ll show you how you can use this model to:</p>
<ul>
<li>create new images</li>
<li>edit existing images</li>
<li>assemble images together</li>
</ul>
<p>For that, I&rsquo;ll be using the <a href="https://github.com/googleapis/java-genai">GenAI Java SDK</a> from Google.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Remark</span>
      </div>
      <div class="admonition-content">
        <p>On this blog, I regularly talk about <a href="https://docs.langchain4j.dev/">LangChain4j</a> and <a href="https://github.com/google/adk-java">ADK for Java</a>.
It&rsquo;s possible to use Nano Banana in ADK, but it&rsquo;s not yet possible to use it with LangChain4j,
because LangChain4j doesn&rsquo;t yet support models that feature output multimodality (i.e. returning text and images).
I&rsquo;ll definitely come back to that once it&rsquo;s supported to show how to call Nano Banana from LangChain4j.</p>
      </div>
    </div><h2 id="creating-your-first-image">Creating your first image</h2>
<p>First of all, you&rsquo;ll need to add the following dependency:</p>
<h3 id="for-maven">For Maven</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.genai<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-genai<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.15.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><h3 id="for-gradle">For Gradle</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>dependencies<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>implementation<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;com.google.genai:google-genai:1.15.0&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next, you&rsquo;ll need to decide if you wish to use a <a href="https://aistudio.google.com/app/apikey">Google AI API key</a>,
or if you have an existing Google Cloud project, and use it for authentication.</p>
<h3 id="using-a-google-ai-api-key">Using a Google AI API key</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="using-a-google-cloud-project">Using a Google Cloud project</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Client<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Client.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CLOUD_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CLOUD_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">vertexAI</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Time to generate our first image!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateContent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;gemini-2.5-flash-image-preview&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        An impressionist oil painting
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        of the port of La Rochelle
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        with its towers and sailing ships.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#4070a0">&#34;TEXT&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;IMAGE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Call the <code>generateContent()</code> method with the name of the model, the text prompt, and some extra configuration to specify that we want both text and image output.</p>
<blockquote>
<p><strong>Note:</strong> When using Nano Banana with Google Cloud Vertex AI, this <code>GenerateContentConfig</code> setting is required, but it&rsquo;s implicit when using the Google AI API endpoint instead.
It&rsquo;s a little discrepancy that might be ironed out later on, but in the meantime, always specify that parameter so that your code works for both flavors of the model and API endpoint.</p></blockquote>
<p>Then you need to save the image part of the response into a file, or potentially stream it to the user.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(Part<span style="color:#bbb"> </span>part<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>Objects.<span style="color:#4070a0">requireNonNull</span>(response.<span style="color:#4070a0">parts</span>()))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(part.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">isPresent</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>blob<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>part.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(blob.<span style="color:#4070a0">data</span>().<span style="color:#4070a0">isPresent</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Files.<span style="color:#4070a0">write</span>(Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;oil.png&#34;</span>),<span style="color:#bbb"> </span>blob.<span style="color:#4070a0">data</span>().<span style="color:#4070a0">get</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(IOException<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>RuntimeException(e);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><blockquote>
<p><strong>Important:</strong> Nano Banana is actually a chat model!
So it can return both text and image.
It may sometimes ask follow up questions to be sure what image it should generate, so an image is not necessarily always present in the output.
But when an image is generated, there&rsquo;s always just one.</p></blockquote>
<p>Let&rsquo;s see what my oil painting looks like:</p>
<p><figure>
  <a href="#img-8982c83d900105a70cdba099b7b77f49">
    <img src="/img/nano-banana/oil.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8982c83d900105a70cdba099b7b77f49">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/oil.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>It looks very much like an oil painting from an impressionist painter!</p>
<h2 id="editing-an-existing-image">Editing an existing image</h2>
<p>An area where Nano Banana excels, it&rsquo;s for editing an existing image.
That&rsquo;s why some people claim it&rsquo;s a Photoshop killer!</p>
<p>To edit an image, you&rsquo;ll have to use a variant of the <code>generateContent()</code> method that takes a <code>Content</code> object (made of <code>Part</code>s),
instead of using the method that takes a simple string, because you&rsquo;ll need to pass:</p>
<ul>
<li>the <strong>image</strong> you want to edit,</li>
<li>as well as the <strong>instructions</strong> of the changes you want to make.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateContent</span>(modelName,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Content.<span style="color:#4070a0">fromParts</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromBytes</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Files.<span style="color:#4070a0">readAllBytes</span>(Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;oil.png&#34;</span>)),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromText</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Simplify this painting to focus on key elements, turn
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            this oil painting into a black and white ink noir comic
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            drawing, make the weather rainy and change the time of
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            the day to be at night.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#4070a0">&#34;TEXT&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;IMAGE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, I pass the <code>oil.png</code> image that we created in the previous run, and add the instructions to change that oil painting into a noir comic style, on a rainy night.
You should also update the file name in which to save the content of the image (I changed it to <code>noir.png</code> in my code).
Let&rsquo;s see if it looks like some noir comic style:</p>
<p><figure>
  <a href="#img-48a0337be8cb6ef352723dc803ec5571">
    <img src="/img/nano-banana/noir.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-48a0337be8cb6ef352723dc803ec5571">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/noir.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Spot on!</p>
<h2 id="combining-several-images">Combining several images</h2>
<p>Another area where Nano Banana is extremely good at, it&rsquo;s for combining several images together.
This capability is very important for example for product marketers who want to maybe put a bottle of perfume in a different decor,
or to make a person do a virtual try on of some new clothes.</p>
<p>Let&rsquo;s say I want to put this person, in the decor, and make her wear the red dress:</p>
<p><figure>
  <a href="#img-e17818491565d38e8c6379b92d54bf9a">
    <img src="/img/nano-banana/images-to-combine.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e17818491565d38e8c6379b92d54bf9a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/images-to-combine.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Let&rsquo;s tweak the <code>generateContent()</code> call again, passing all three images, and the editing instructions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>client.<span style="color:#4070a0">models</span>.<span style="color:#4070a0">generateContent</span>(modelName,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Content.<span style="color:#4070a0">fromParts</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromBytes</span>(Files.<span style="color:#4070a0">readAllBytes</span>(Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;decor.jpg&#34;</span>)),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/jpeg&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromBytes</span>(Files.<span style="color:#4070a0">readAllBytes</span>(Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;taylor.jpg&#34;</span>)),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/jpeg&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromBytes</span>(Files.<span style="color:#4070a0">readAllBytes</span>(Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;red-dress.png&#34;</span>)),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Part.<span style="color:#4070a0">fromText</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Add this person to the exterior decor,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            and make her wear the red dress.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentConfig.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseModalities</span>(<span style="color:#4070a0">&#34;TEXT&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;IMAGE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>The result speaks for itself:</p>
<p><figure>
  <a href="#img-0b148b8da331fd702855363233f9505c">
    <img src="/img/nano-banana/combined.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0b148b8da331fd702855363233f9505c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nano-banana/combined.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Next time, maybe I&rsquo;ll use better lighting!</p>
<h2 id="conclusion-and-going-further">Conclusion and going further</h2>
<p>I hope you enjoyed this quick tutorial on <strong>how to generate and edit images with Nano Banana in Java</strong>!
It&rsquo;s a very fun model to use, so don&rsquo;t hesitate to unleash your creativity.
And again, you don&rsquo;t need to be a &#x1f40d; Python developer to do that!
<strong>You can do everything in Java</strong> &#x2615;</p>
<p>In the meantime, have fun generating cool images with Nano Banana! In particular from Java!
And don&rsquo;t hesitate to share them with me on social media (links at the bottom of this blog),
I&rsquo;m curious to see your creations!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>In-browser semantic search with EmbeddingGemma</title><link>https://glaforge.dev/posts/2025/09/08/in-browser-semantic-search-with-embeddinggemma/</link><pubDate>Mon, 08 Sep 2025 09:04:29 +0200</pubDate><guid>https://glaforge.dev/posts/2025/09/08/in-browser-semantic-search-with-embeddinggemma/</guid><description>&lt;p>A few days ago, Google DeepMind released a new embedding model based on the Gemma open weight model: &lt;a href="https://developers.googleblog.com/en/introducing-embeddinggemma/?utm_campaign=CDR_0x7a40493f_default_b443675976&amp;amp;utm_medium=external&amp;amp;utm_source=blog">EmbeddingGemma&lt;/a>.
With &lt;strong>308 million parameters&lt;/strong>, such a model is tiny enough to be able to &lt;strong>run on edge devices&lt;/strong> like your phone, tablet, or your computer.&lt;/p>
&lt;p>Embedding models are the cornerstone of &lt;a href="https://glaforge.dev/tags/retrieval-augmented-generation">Retrieval Augmented Generation&lt;/a> systems (RAG),
and what generally powers &lt;strong>semantic search&lt;/strong> solutions.
Being able to run an embedding model &lt;em>locally&lt;/em> means you don&amp;rsquo;t need to rely on a server (no need to send your data over the internet): this is great for privacy.
And of course, cost is reduced as well, because you don&amp;rsquo;t need to pay for a remote / hosted embedding model.&lt;/p></description><content:encoded>
<![CDATA[<p>A few days ago, Google DeepMind released a new embedding model based on the Gemma open weight model: <a href="https://developers.googleblog.com/en/introducing-embeddinggemma/?utm_campaign=CDR_0x7a40493f_default_b443675976&amp;utm_medium=external&amp;utm_source=blog">EmbeddingGemma</a>.
With <strong>308 million parameters</strong>, such a model is tiny enough to be able to <strong>run on edge devices</strong> like your phone, tablet, or your computer.</p>
<p>Embedding models are the cornerstone of <a href="/tags/retrieval-augmented-generation">Retrieval Augmented Generation</a> systems (RAG),
and what generally powers <strong>semantic search</strong> solutions.
Being able to run an embedding model <em>locally</em> means you don&rsquo;t need to rely on a server (no need to send your data over the internet): this is great for privacy.
And of course, cost is reduced as well, because you don&rsquo;t need to pay for a remote / hosted embedding model.</p>
<p>In this article, I&rsquo;ll walk you through how I built a simple semantic search application.
This web app allows users to add a collection of documents, type a query, and instantly get a ranked list of the most relevant documents based on their semantic similarity.</p>
<p><figure>
  <a href="#img-a4371863e283683bef99584076060cbf">
    <img src="/img/embedding-gemma/embedding-gemma-semantic-search.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a4371863e283683bef99584076060cbf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/embedding-gemma/embedding-gemma-semantic-search.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I&rsquo;ll show you how I brought this to life using the following stack:</p>
<ul>
<li><strong>The embedding model:</strong> Google&rsquo;s new, lightweight <strong>EmbeddingGemma</strong> model.</li>
<li><strong>The inference engine:</strong> 🤗 HuggingFace&rsquo;s <strong>Transformers.js</strong> library, which runs the model directly in the browser.</li>
<li><strong>The UI:</strong> A simple and clean interface built with <strong>Vite, React, and Tailwind CSS</strong>.</li>
<li><strong>The deployment:</strong> A fully automated CI/CD pipeline using <strong>GitHub Actions</strong> to deploy the static site to 🔥 <strong>Firebase Hosting</strong>.</li>
</ul>
<p>Ready to see how it&rsquo;s done? Let&rsquo;s dive in.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Tip</span>
      </div>
      <div class="admonition-content">
        <p>For those who are in a hurry, feel free to check out the live demo or browse the source code on GitHub:</p>
<ul>
<li><strong>Live Demo:</strong> <a href="https://embedding-gemma.web.app/">https://embedding-gemma.web.app/</a></li>
<li><strong>GitHub Repository:</strong> <a href="https://github.com/glaforge/embedding-gemma-semantic-search">https://github.com/glaforge/embedding-gemma-semantic-search</a></li>
</ul>
<p>For the demo, 1️⃣ first click to load the weights of the model, 2️⃣ then add a few documents in the <em>database</em>, 3️⃣ finally you can ask a question, 4️⃣ and find the most relevant documents.</p>
      </div>
    </div><h2 id="why-run-ai-in-the-browser">Why run AI in the browser?</h2>
<p>Running an AI model (here an embedding model) directly on the client-side might seem unconventional considering the best models are usually too big to run on edge devices, but it offers a compelling set of advantages, especially for applications like this one:</p>
<ul>
<li><strong>Privacy:</strong> Since all the data processing and embedding calculations happen on the user&rsquo;s device, no sensitive information ever leaves the browser. The documents and queries are never sent over the network, making it a perfect solution for applications that handle personal or confidential text.</li>
<li><strong>Zero added server costs:</strong> The <em>&ldquo;backend&rdquo;</em> is the user&rsquo;s browser. No need for an expensive GPU-powered servers to run the AI model. The application itself is just a set of static files, which can be hosted for free on services like 🔥 Firebase Hosting or GitHub Pages. Of course, the <em>rest</em> of your application may need servers, but at least this part isn&rsquo;t tied to a server.</li>
<li><strong>Low latency:</strong> With the model running locally, there&rsquo;s no network round-trip to a server. Once the model weights are loaded in memory, search queries are processed instantly, providing a snappy and responsive user experience. Well, at least as long as you don&rsquo;t have many millions of documents to search through, as it&rsquo;ll scale linearly without a proper vector database.</li>
<li><strong>Offline-first capability:</strong> After the initial load, the entire application and the AI model can be cached by the browser (and the data be stored in the browser&rsquo;s database or local storage), allowing it to function perfectly even without an internet connection.</li>
</ul>
<h2 id="the-core-components-a-model-and-a-library">The core components: a model and a library</h2>
<p>At the heart of my application are two key pieces of technology that make in-browser semantic search possible:</p>
<h3 id="the-model-embeddinggemma">The model: <code>EmbeddingGemma</code></h3>
<p>The &ldquo;brain&rdquo; of the search is <strong>EmbeddingGemma</strong>, the new, state-of-the-art (SOTA) text embedding model from Google. Unlike massive language models designed for generating text, embedding models are specialized for a different task: converting a piece of text into a numerical vector (a list of numbers). This vector represents the text&rsquo;s semantic meaning. <strong>The closer two vectors are to each other in mathematical space, the more similar their meanings are.</strong></p>
<p>EmbeddingGemma is the perfect choice for this project for several reasons:</p>
<ul>
<li><strong>High performance, small size:</strong> It is the highest-performing model of its size (under 500M parameters) on the multilingual <a href="https://huggingface.co/spaces/mteB/leaderboard">Massive Text Embedding Benchmark (MTEB)</a>. Built on the <a href="https://deepmind.google/models/gemma/gemma-3/">Gemma 3</a> architecture, it&rsquo;s designed for on-device applications where resources are limited.</li>
<li><strong>On-device efficiency:</strong> With quantization, the model&rsquo;s memory footprint can be less than 200MB, making it ideal for running in a web browser without overwhelming the user&rsquo;s device.</li>
<li><strong>Matryoshka Representation Learning (MRL):</strong> While the model produces a high-quality, full-size embedding with 768 dimensions, <a href="https://huggingface.co/blog/matryoshka">MRL</a> allows us to truncate that vector to a smaller size (512, 256, or 128 dimensions) with a minimal loss in accuracy. This gives us a good trade-off between performance and computational cost. In my application, I use the first 128 dimensions for the vector visualizations, which is a perfect example of MRL in action.</li>
<li><strong>Multilingual support:</strong> The model was trained on data from over 100 languages. This is quite rare for an embedding model of that size to be good across a wide variety of spoken languages.</li>
</ul>
<h3 id="the-library-transformersjs">The library: <code>Transformers.js</code></h3>
<p>The &ldquo;engine&rdquo; that runs the model is <a href="https://huggingface.co/docs/transformers.js/index">Transformers.js</a> from 🤗 HuggingFace. This cool JavaScript library is designed to run a wide variety of popular AI models directly in the browser. It handles all the complex, low-level work of loading the model, managing the cache, and executing the computations efficiently using the browser&rsquo;s and device&rsquo;s capabilities.</p>
<p>Transformers.js make it simple to run a model like EmbeddingGemma on the client-side. With it, as shown in 🤗 HuggingFace&rsquo;s <a href="https://huggingface.co/blog/embeddinggemma">blog post</a>; it only takes a few lines of code to get a model up and running, as we&rsquo;ll see in the next section.</p>
<h2 id="how-the-code-works">How the code works</h2>
<p>While the UI is a standard React application built with Vite and styled with Tailwind CSS, the most interesting part is the <code>embeddingService.ts</code> file, which acts as a wrapper around the Transformers.js library.</p>
<blockquote>
<p>I actually vibe-coded the whole application thanks to <a href="https://ai.dev/">Google AI Studio</a> and <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a>.
As I&rsquo;m not an expert in React or TypeScript, that was easier to guide Gemini to make it create the UI I wanted, and I fed Gemini also the code in the 🤗 HuggingFace article to get started with the inference.</p></blockquote>
<p>Let&rsquo;s look at a simplified version of the core logic.</p>
<h3 id="initializing-the-model">Initializing the model</h3>
<p>First, I needed to create a singleton instance of the service. This ensures I only ever initialize one copy of the model. The <code>getInstance</code> method handles this, and the <code>init</code> method does the heavy lifting, by calling the <code>AutoTokenizer.from_pretrained()</code> and <code>AutoModel.from_pretrained()</code> method, for loading the text tokenizer and the model respectively.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> { AutoTokenizer, AutoModel } <span style="color:#007020;font-weight:bold">from</span> <span style="color:#4070a0">&#34;@huggingface/transformers&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> EmbeddingService {
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">private</span> tokenizer: <span style="color:#902000">AutoTokenizer</span> <span style="color:#666">|</span> <span style="color:#007020;font-weight:bold">null</span> <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">null</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">private</span> model: <span style="color:#902000">AutoModel</span> <span style="color:#666">|</span> <span style="color:#007020;font-weight:bold">null</span> <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">null</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// ... singleton logic ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">private</span> <span style="color:#007020;font-weight:bold">async</span> init() {
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// Load the tokenizer and model from the /model/ directory
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">this</span>.tokenizer <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> AutoTokenizer.from_pretrained(<span style="color:#4070a0">&#34;/model/&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">this</span>.model <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> AutoModel.from_pretrained(<span style="color:#4070a0">&#34;/model/&#34;</span>, {
</span></span><span style="display:flex;"><span>      dtype<span style="color:#666">:</span> <span style="color:#4070a0">&#34;q4&#34;</span>, <span style="color:#60a0b0;font-style:italic">// Use a quantized version for efficiency
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    });
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>The model can also be loaded from 🤗 HuggingFace&rsquo;s Hub, but I wanted the model to be local as well, for a faster loading experience and for a full local-first approach.</p>
      </div>
    </div><h3 id="generating-embeddings-and-calculating-similarity">Generating embeddings and calculating similarity</h3>
<p>Once the model is ready, it can be fed the query and documents. The model expects specific prefixes for queries and documents to perform best (<code>task: search result | query:</code> and <code>title: none | text:</code>), so I made sure to add those first.</p>
<p>The core steps are:</p>
<ol>
<li><strong>Tokenize:</strong> Convert the text (query and documents) into tokens that the model can understand.</li>
<li><strong>Embed:</strong> Pass the tokens to the model to get the sentence embeddings.</li>
<li><strong>Calculate similarity:</strong> Use a matrix multiplication (<code>matmul</code>) of the embeddings with their transpose to get a similarity score between the query and every document.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// ... inside the EmbeddingService class ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">async</span> embed(query: <span style="color:#902000">string</span>, documents: <span style="color:#902000">string</span>[]) {
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#60a0b0;font-style:italic">// Add the required prefixes for the model
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#007020;font-weight:bold">const</span> prefixedQuery <span style="color:#666">=</span> <span style="color:#4070a0">&#34;task: search result | query: &#34;</span> <span style="color:#666">+</span> query;
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> prefixedDocs <span style="color:#666">=</span> documents.map(doc <span style="color:#666">=&gt;</span> <span style="color:#4070a0">&#34;title: none | text: &#34;</span> <span style="color:#666">+</span> doc);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// 1. Tokenize the inputs
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#007020;font-weight:bold">const</span> inputs <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> <span style="color:#007020;font-weight:bold">this</span>.tokenizer([prefixedQuery, ...prefixedDocs], {
</span></span><span style="display:flex;"><span>    padding: <span style="color:#902000">true</span>,
</span></span><span style="display:flex;"><span>    truncation: <span style="color:#902000">true</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// 2. Get the sentence embeddings
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#007020;font-weight:bold">const</span> { sentence_embedding } <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> <span style="color:#007020;font-weight:bold">this</span>.model(inputs);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// 3. Calculate the similarity scores
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#007020;font-weight:bold">const</span> scores <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> matmul(sentence_embedding, sentence_embedding.transpose(<span style="color:#40a070">1</span>, <span style="color:#40a070">0</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// The first row of the scores matrix contains the similarity
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#60a0b0;font-style:italic">// of the query to all other documents.
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  <span style="color:#007020;font-weight:bold">const</span> similarities <span style="color:#666">=</span> (scores.tolist() <span style="color:#007020;font-weight:bold">as</span> <span style="color:#902000">number</span>[][])[<span style="color:#40a070">0</span>].slice(<span style="color:#40a070">1</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#60a0b0;font-style:italic">// ... logic to rank documents based on scores ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>}
</span></span></code></pre></div><p>And that&rsquo;s the core of it! With just these two methods (<code>init()</code> and <code>embed()</code>), I had a fully functional semantic search engine running in the browser.</p>
<h2 id="visualizing-the-embeddings">Visualizing the embeddings</h2>
<p>To make the concept of semantic similarity more tangible, I added a simple visualization for each document&rsquo;s embedding vector and the search query.
As soon as you type a few characters in the search query input field, or when you add a new document, you&rsquo;ll see a colored representation of its vector.</p>
<p><figure>
  <a href="#img-1fd30944dd89407a2753e3adf47fff2c">
    <img src="/img/embedding-gemma/embedding-gemma-visualization.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1fd30944dd89407a2753e3adf47fff2c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/embedding-gemma/embedding-gemma-visualization.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Each cell in the visualization represents one of the numbers in the embedding vector, with its color intensity indicating the value. When you compare the visualizations of the query and a relevant document, you can often spot visual similarities in their patterns, offering an intuitive glimpse into how the model &ldquo;sees&rdquo; the relationships between texts.</p>
<p>This is where the <a href="https://arxiv.org/abs/2205.13147">Matryoshka Representation Learning</a> (MRL) feature of EmbeddingGemma truly shines. The full embedding vector has 768 dimensions, which would be too much to display effectively. Thanks to MRL, I can use just the first 128 dimensions of the vector for this visualization with a minimal loss of semantic information. This provides a compact and meaningful visual fingerprint of the text&rsquo;s meaning.</p>
<h2 id="the-deployment-pipeline-cicd-with-a-twist">The deployment pipeline: CI/CD with a twist</h2>
<p>A key challenge with this project was handling the model files.
Although I&rsquo;m using <em>quantized</em> versions of the weights, they are still quite heavy in megabytes, which is far too large to commit to a Git repository.
However, the application <em>needs</em> these files to be present in the <code>public/model</code> directory to function.</p>
<p>I solved this with a clever CI/CD pipeline using GitHub Actions.
Instead of storing the model in my repository, I download it on-the-fly during the deployment process.
Since the model files are local, when the application starts, they are faster to load than when loading them from 🤗 HuggingFace&rsquo;s hub.</p>
<p>Here’s the relevant snippet from my <code>.github/workflows/firebase-hosting-merge.yml</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">jobs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">build_and_deploy</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">runs-on</span>:<span style="color:#bbb"> </span>ubuntu-latest<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">uses</span>:<span style="color:#bbb"> </span>actions/checkout@v4<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>Download Model<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">run</span>:<span style="color:#bbb"> </span>|<span style="color:#4070a0;font-style:italic">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">          sudo apt-get install git-lfs
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">          git lfs install
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">          git clone https://huggingface.co/onnx-community/embeddinggemma-300m-ONNX public/model</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">run</span>:<span style="color:#bbb"> </span>npm ci &amp;&amp; npm run build<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">uses</span>:<span style="color:#bbb"> </span>FirebaseExtended/action-hosting-deploy@v0<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">with</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">repoToken</span>:<span style="color:#bbb"> </span>${{ secrets.GITHUB_TOKEN }}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">firebaseServiceAccount</span>:<span style="color:#bbb"> </span>${{ secrets.FIREBASE_SERVICE_ACCOUNT_EMBEDDING_GEMMA }}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#60a0b0;font-style:italic"># ... other options</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>This workflow does the following every time I push to the <code>main</code> branch:</p>
<ol>
<li>Checks out my code.</li>
<li><strong>Downloads the model:</strong> It installs <code>git-lfs</code> and clones the model files directly from the 🤗 HuggingFace Hub into the <code>public/model</code> directory. Note that it&rsquo;s certainly possible to cache the outcome of the model weight checkout though, to make the CI/CD pipeline snappier.</li>
<li><strong>Builds the app:</strong> It runs <code>npm ci &amp;&amp; npm run build</code>, which creates the static <code>dist</code> folder. Because the model is now in the <code>public</code> directory, Vite automatically includes it in the build output.</li>
<li><strong>Deploys:</strong> It sends the final <code>dist</code> folder, now containing the model, up to 🔥 Firebase Hosting.</li>
</ol>
<p>This approach gives me the best of both worlds: a lightweight Git repository and a fully functional, self-contained application deployed to users.</p>
<h2 id="conclusion">Conclusion</h2>
<p><strong>Google AI Studio</strong> and the <strong>Gemini CLI</strong> allowed me to easily build a demonstration app for running <strong>EmbeddingGemma</strong> on the client-side via <strong>Transformers.js</strong>.</p>
<p>This application requires <strong>no server</strong> (apart from the the static assets hosting), is <strong>privacy-focused</strong> (your data is never sent over the internet), and is <strong>cost-effective</strong> as its semantic search engine runs entirely in the browser.</p>
<p>I hope this project demonstrates the <strong>growing potential of client-side AI and small models</strong> (both embedding and language models).</p>
<p>I encourage you to explore the project and see it in action for yourself:</p>
<ul>
<li><strong>Live Demo:</strong> <a href="https://embedding-gemma.web.app/">https://embedding-gemma.web.app/</a>
<ul>
<li><em>Note: The first time you open the application, it will need to load the model files. You&rsquo;ll see a loading indicator, and once it&rsquo;s complete, you&rsquo;ll be able to start searching.</em></li>
</ul>
</li>
<li><strong>GitHub Repository:</strong> <a href="https://github.com/glaforge/embedding-gemma-semantic-search">https://github.com/glaforge/embedding-gemma-semantic-search</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Vibe-coding a Chrome extension with Gemini CLI to summarize articles</title><link>https://glaforge.dev/posts/2025/08/06/vibe-coding-a-chrome-extension-with-gemini-cli-to-summarize-articles/</link><pubDate>Wed, 06 Aug 2025 13:29:54 +0200</pubDate><guid>https://glaforge.dev/posts/2025/08/06/vibe-coding-a-chrome-extension-with-gemini-cli-to-summarize-articles/</guid><description>&lt;p>I often find myself staring at a wall of text online.
It could be a lengthy technical article, a detailed news report, or a deep-dive blog post.
My first thought is often: &lt;em>&amp;ldquo;Is this worth the time to read in full?&amp;rdquo;&lt;/em>
On top of that, for my podcast, &lt;a href="https://lescastcodeurs.com/">Les Cast Codeurs&lt;/a>, I&amp;rsquo;m constantly gathering links and need to create quick shownotes, which is essentially&amp;hellip; a summary.&lt;/p>
&lt;p>My first attempt to solve this was a custom &lt;a href="https://gemini.google/overview/gems/">Gemini Gems&lt;/a> I created: a personalized chatbot that could summarize links.
It worked, but I often ran into a wall: it couldn&amp;rsquo;t access paywalled content, pages that required a login, or dynamically generated sites that I was already viewing in my browser.
The solution was clear: I needed to bring the summarization &lt;em>to&lt;/em> the content, not the other way around.
The idea for a Chrome extension was born.&lt;/p></description><content:encoded>
<![CDATA[<p>I often find myself staring at a wall of text online.
It could be a lengthy technical article, a detailed news report, or a deep-dive blog post.
My first thought is often: <em>&ldquo;Is this worth the time to read in full?&rdquo;</em>
On top of that, for my podcast, <a href="https://lescastcodeurs.com/">Les Cast Codeurs</a>, I&rsquo;m constantly gathering links and need to create quick shownotes, which is essentially&hellip; a summary.</p>
<p>My first attempt to solve this was a custom <a href="https://gemini.google/overview/gems/">Gemini Gems</a> I created: a personalized chatbot that could summarize links.
It worked, but I often ran into a wall: it couldn&rsquo;t access paywalled content, pages that required a login, or dynamically generated sites that I was already viewing in my browser.
The solution was clear: I needed to bring the summarization <em>to</em> the content, not the other way around.
The idea for a Chrome extension was born.</p>
<p>This got me thinking: what if I had a browser extension that could give me the gist of any page with a single click?
I had the idea and the need, but one small problem: I&rsquo;d never built a Chrome extension before.</p>
<p>This project became an experiment in the trendy <em>&ldquo;vibe-coding&rdquo;</em> approach, and my partner in crime was the
<strong><a href="https://developers.google.com/gemini/cli?utm_campaign=CDR_0x7a40493f_default_b436838088&amp;utm_medium=external&amp;utm_source=blog">Gemini CLI</a></strong>.
I didn&rsquo;t start by reading pages of documentation.
Instead, I had a clear vision for what I wanted and decided to build it interactively.</p>
<p>What made the process so unique was that the Gemini CLI was incredibly proactive.
From the very beginning, when I just created an empty directory for the project, it immediately understood my intent.
Before I even had a chance to ask, it suggested that I probably wanted to create a Chrome extension for summarization and laid out a full plan, just by infering the intent because of the actual name of the directory I had created!</p>
<p>Throughout the development, it took liberties, suggesting better ways to handle the API key setup and proposing new features.
It felt less like giving instructions and more like a productive collaboration between a human and an AI.
The result is the <a href="https://github.com/glaforge/chrome-gemini-summarize-extension">Gemini summarizer Chrome extension</a>, and it was built entirely <em>on vibes</em>.</p>
<p>Of course, not everything is always roses and bloom, like when Gemini CLI reverted some of my manual changes (which lead me to warn it that I had actually made some manual changes, so that it could update its internal context), or when it somehow ran into a loop and not obeying my command, for some reason I couldn&rsquo;t really figure out.
Restarting it made it work: maybe like us, humans, it needs a break once in a while?
I&rsquo;m probably <em>anthropomorphising</em> too much, but overall, that was a productive experience!</p>
<h2 id="under-the-hood-powered-by-gemini">Under the hood: Powered by Gemini</h2>
<p>The core of this extension is, of course, the Google Gemini API. It&rsquo;s responsible for the powerful summarization capabilities.
To use the extension, you&rsquo;ll need to provide your own <a href="https://aistudio.google.com/app/apikey">Gemini API key</a>.
Don&rsquo;t worry, it&rsquo;s stored securely and locally in your browser&rsquo;s storage and is only used to communicate with the official Google AI endpoints.</p>
<h2 id="features-that-evolved-from-the-_vibe_">Features that evolved from the <em>vibe</em></h2>
<p>What started as a simple idea quickly grew as we built it. Here&rsquo;s what the extension can do:</p>
<ul>
<li><strong>Summarize web pages</strong>: Click the button to get a summary of the main article on a page.</li>
<li><strong>Summarize selected text only</strong>: If you highlight a specific piece of text, the extension is smart enough to summarize only your selection.</li>
<li><strong>Real-time streaming</strong>: The summary doesn&rsquo;t just appear; it streams in word-by-word, making the experience feel fast and responsive.</li>
<li><strong>Go shorter</strong>: Got a summary but want it even more concise? The <em>&ldquo;Shrink&rdquo;</em> button re-summarizes the summary to be as brief as possible.</li>
<li><strong>Speak my language</strong>: A simple toggle lets you get your summary in either English or French <em>(I&rsquo;m writing shownotes for the podcast in French).</em></li>
<li><strong>Copy with formatting</strong>: The copy button copies the summary as rich text, preserving all the formatting like bolding and bullet points. That way, it&rsquo;s easy to paste in a Google Docs, in a Slack channel or wherever you want.</li>
</ul>
<h2 id="potential-future-evolutions">Potential future evolutions</h2>
<p>This project is fully functional (at least for my personal need) but there are always more <em>vibes</em> to explore. Here are a few ideas for where this could go next:</p>
<ol>
<li><strong>Publish to the Chrome Web Store</strong>: To make installation as easy as a single click for any user.</li>
<li><strong>Expanded language support</strong>: Add a dropdown or input field to allow summarization in any language Gemini supports.</li>
<li><strong>Customizable prompts</strong>: Allow users to tweak the summarization prompts directly in the options page to tailor the style of the summaries to their exact needs. And maybe even support multiple prompts at the same time, that you can choose from another dropdown.</li>
</ol>
<h2 id="get-the-code-and-vibe-code-your-own-variant">Get the code and vibe code your own variant</h2>
<p>This entire project is open-source and available on GitHub. I encourage you to check it out, install it for yourself, and see how it feels.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>&#x27a1;&#xfe0f; <strong><a href="https://github.com/glaforge/chrome-gemini-summarize-extension">View the project on GitHub</a></strong></p>
      </div>
    </div><p>The <a href="https://github.com/glaforge/chrome-gemini-summarize-extension?tab=readme-ov-file#installation">installation</a>
is straightforward for developers, with instructions in the <code>README.md</code> file — until I decide it&rsquo;s worth sharing it more broadly via the Chrome extension store!</p>
<p>More importantly, I encourage you to take this as inspiration.
If you have an idea, even if you don&rsquo;t know the technology, try vibe-coding it.
I now find myself starting projects I wouldn&rsquo;t have dared to before because I wasn&rsquo;t familiar with the language, framework, or patterns.
I&rsquo;d think of a cool app idea but wouldn&rsquo;t even start, knowing it would take too many days or evenings just to get something basic going.
With vibe-coding, I can experiment and get a working prototype in a fraction of the time.</p>
<p>In the <em>pure</em> vibe-coding approach as <a href="https://x.com/karpathy/status/1886192184808149383">coined by Andrej Karpathy</a>,
you wouldn&rsquo;t even look at the generated code at all.
But I&rsquo;m a developer, I can&rsquo;t help it, and will always look at how my AI coding agent does its magic.</p>
<p>I didn&rsquo;t know how Chrome extensions were defined and worked, but now, I&rsquo;m more knowledgeable because I had the chance to witness how to build such an extension, and see the project structure, the key files needed (<code>manifest.json</code>), the use of service workers, how to store a secret like an API key in Chrome&rsquo;s storage, etc. It&rsquo;s a <strong>great learning opportunity where you actually look at the generated vibed-coded code</strong>!</p>
<p>That&rsquo;s the real power of this new way of building software: a great model like
<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models?utm_campaign=CDR_0x7a40493f_default_b436838088&amp;utm_medium=external&amp;utm_source=blog">Gemini</a> paired with a great tool like the
<a href="https://developers.google.com/gemini/cli?utm_campaign=CDR_0x7a40493f_default_b436838088&amp;utm_medium=external&amp;utm_source=blog">Gemini CLI</a>.
It&rsquo;s a game-changer.
Feel free to <a href="https://github.com/glaforge/chrome-gemini-summarize-extension">fork this project</a>, extend it, or build your own extension from scratch.
Happy vibe-coding!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Visualizing ADK multiagent systems</title><link>https://glaforge.dev/posts/2025/08/01/visualizing-adk-multiagent-systems/</link><pubDate>Fri, 01 Aug 2025 15:07:09 +0200</pubDate><guid>https://glaforge.dev/posts/2025/08/01/visualizing-adk-multiagent-systems/</guid><description>&lt;p>Let me share an interesting experiment I worked on to visualize your AI agent structure, more specifically, &lt;a href="https://github.com/google/adk-java">Agent Development Kit&lt;/a> (ADK) multiagents.&lt;/p>
&lt;p>The more complex your agents become, as you split tasks and spin off more specialized and focused sub-agents, the harder it is to see what your system is really made of, and how the interactions happen between the various components.&lt;/p>
&lt;p>This is also something I experienced when I was &lt;a href="https://glaforge.dev/tags/workflows/">covering&lt;/a> &lt;a href="https://cloud.google.com/workflows">Google Cloud Workflows&lt;/a>: the more steps in the workflow, the more loops I had, indirections, conditions, etc, the trickier it was to understand and debug.
And sometimes, as the saying goes, a picture is worth a thousand words.
So when I was working on my recent &lt;a href="https://glaforge.dev/posts/2025/07/29/mastering-agentic-workflows-with-adk-the-recap/">series of articles on ADK agentic workflows&lt;/a>
(drawing diagrams by hand) this idea of experimenting with an ADK agent &lt;em>visualizer&lt;/em> came up immediately.&lt;/p></description><content:encoded>
<![CDATA[<p>Let me share an interesting experiment I worked on to visualize your AI agent structure, more specifically, <a href="https://github.com/google/adk-java">Agent Development Kit</a> (ADK) multiagents.</p>
<p>The more complex your agents become, as you split tasks and spin off more specialized and focused sub-agents, the harder it is to see what your system is really made of, and how the interactions happen between the various components.</p>
<p>This is also something I experienced when I was <a href="https://glaforge.dev/tags/workflows/">covering</a> <a href="https://cloud.google.com/workflows">Google Cloud Workflows</a>: the more steps in the workflow, the more loops I had, indirections, conditions, etc, the trickier it was to understand and debug.
And sometimes, as the saying goes, a picture is worth a thousand words.
So when I was working on my recent <a href="https://glaforge.dev/posts/2025/07/29/mastering-agentic-workflows-with-adk-the-recap/">series of articles on ADK agentic workflows</a>
(drawing diagrams by hand) this idea of experimenting with an ADK agent <em>visualizer</em> came up immediately.</p>
<p>Let me introduce you to my <strong>ADK Agent Code Visualizer</strong>, a tool designed to give you a clear, visual representation of your agent-based applications built with the <a href="https://developers.google.com/ai/agent-development-kit?utm_campaign=CDR_0x7a40493f_default_b435663830&amp;utm_medium=external&amp;utm_source=blog">Agent Development Kit (ADK)</a>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Disclaimer</span>
      </div>
      <div class="admonition-content">
        <p>This is an experiment, not really something I intend to develop that much further.
It works fine for single-file systems that you can easily copy&rsquo;n paste in the visualizer.
But it won&rsquo;t understand your current project structure, and your agents spanning multipe files.</p>
<p>&#x27a1;&#xfe0f; That said, for those who want to play with it already without further ado, please head over to this online version of my
<a href="https://adk-agent-code-visualizer-1029513523185.europe-west1.run.app/">ADK code visualizer</a>.</p>
<p>&#x1f4c2; I&rsquo;ve also pushed the code of this <a href="https://github.com/glaforge/adk-agent-code-visualizer">project to GitHub</a>
if you want to take a look or further extend it.</p>
      </div>
    </div><h2 id="from-vibe-coding-to-cloud-deployment">From vibe-coding to cloud deployment</h2>
<p>The journey of this project is a testament to the power of modern AI-assisted development.
It all started in <a href="https://aistudio.google.com/">Google AI Studio</a>, where I <em>&ldquo;vibe-coded&rdquo;</em> the initial concept.
I provided a high-level description of what I wanted to build, and AI Studio generated the foundational React and Node.js code.
From there, I moved to my local environment and used <a href="https://cloud.google.com/gemini/docs/codeassist/gemini-cli?utm_campaign=CDR_0x7a40493f_default_b435663830&amp;utm_medium=external&amp;utm_source=blog">Gemini CLI</a>
for the iterative development process—adding features, refining the logic, and fixing bugs.
The entire development experience was a seamless collaboration between human and AI. Yeah!</p>
<p>Finally, the application was deployed on <strong>Google Cloud</strong>, more specifically containerized on <a href="https://cloud.google.com/run?utm_campaign=CDR_0x7a40493f_default_b435663830&amp;utm_medium=external&amp;utm_source=blog">Cloud Run</a>
making it accessible for everyone to use.</p>
<h2 id="what-it-does">What it does</h2>
<p>The ADK Agent Code Visualizer takes a single source file (Python or Java) containing an ADK agent definition and generates an interactive graph
(using <a href="https://reactflow.dev/">ReactFlow</a>).
This visualization helps you understand the architecture of your multi-agent system at a glance.</p>
<p>Here’s what the visualization highlights:</p>
<ul>
<li><strong>Agent relationships:</strong> See how your agents are connected. <a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">Sub-agents</a> are clearly marked with arrows indicating the flow of control.</li>
<li><strong>Tool usage:</strong> If an agent uses one or more <a href="https://glaforge.dev/posts/2025/06/15/expanding-ai-agent-capabilities-with-tools/">tools</a>, it&rsquo;s indicated on the graph, giving you insight into the agent&rsquo;s capabilities.</li>
<li><strong>Callbacks:</strong> The visualization also flags when callbacks are defined for an agent.</li>
<li><strong>Orchestration patterns:</strong> The graph shows whether agents are configured to run:
<ul>
<li><strong>Sequentially:</strong> <a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">One after another</a>.</li>
<li><strong>In parallel:</strong> Executing <a href="https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/">concurrently</a>.</li>
<li><strong>In a loop:</strong> <a href="https://glaforge.dev/posts/2025/07/28/mastering-agentic-workflows-with-adk-loop-agents/">Repeating a series of actions</a>.</li>
</ul>
</li>
</ul>
<p>The visualization is composed of four main types of nodes (with different color codes), each representing a different kind of agent:</p>
<ul>
<li><code>LlmAgent</code>: A plain agent node.</li>
<li><code>SequentialAgent</code>: For agents that execute in a sequence.</li>
<li><code>ParallelAgent</code>: For agents that run concurrently.</li>
<li><code>LoopAgent</code>: For agents that repeat actions in a loop.</li>
</ul>
<p>Whenever there are sub-agents or linked agents, you&rsquo;ll see arrows pointing from the parent or preceding agent to the next one in the flow, making the orchestration logic easy to follow.</p>
<h3 id="dark-mode">Dark mode!</h3>
<p>And for all the nerds who prefer dark mode, you can easily switch between light and dark modes!</p>
<p><figure>
  <a href="#img-77d157d928073605b17fe814341d7356">
    <img src="/img/adk/visualizer-dark-light-mode.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-77d157d928073605b17fe814341d7356">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/visualizer-dark-light-mode.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="limitations">Limitations</h2>
<p>As I said in introduction, it&rsquo;s important to note a key limitation of the current version: <strong>the tool analyzes single source files only.</strong>
This is more of a proof-of-concept than a finalized product.
This means it does not parse an entire multi-file project to resolve dependencies or definitions across different files
(externalizing prompts, splitting in different files or directories, etc).</p>
<p>Despite this, the visualizer works pretty well for the kind of self-contained examples you&rsquo;ll find in the official
<a href="https://google.github.io/adk-docs/">ADK documentation</a>
and the associated <a href="https://github.com/google/adk-samples">adk-samples GitHub repository</a>.
It&rsquo;s a perfect companion for learning ADK or for quickly mapping out the structure of your single-file agent orchestrations.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The ADK Agent Code Visualizer is a simple yet powerful tool for anyone working with the Agent Development Kit.
It streamlines the process of understanding complex agent interactions and serves as a great example of how modern AI tools like
<a href="https://aistudio.google.com/">Google AI Studio</a>, and
<a href="https://cloud.google.com/gemini/docs/codeassist/gemini-cli?utm_campaign=CDR_0x7a40493f_default_b435663830&amp;utm_medium=external&amp;utm_source=blog">Gemini CLI</a> can accelerate the development lifecycle from idea to deployment.</p>
<p>Feel free to check it out and visualize your own agent creations!
And tell me what you think!</p>
<p>&#x27a1;&#xfe0f; You can <a href="https://adk-agent-code-visualizer-1029513523185.europe-west1.run.app/">try it online</a>
and check out the <a href="https://github.com/glaforge/adk-agent-code-visualizer">source code</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Mastering agentic workflows with ADK: the recap</title><link>https://glaforge.dev/posts/2025/07/29/mastering-agentic-workflows-with-adk-the-recap/</link><pubDate>Tue, 29 Jul 2025 08:51:28 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/29/mastering-agentic-workflows-with-adk-the-recap/</guid><description>&lt;p>Over the past few articles, we&amp;rsquo;ve taken a deep dive into the powerful &lt;strong>agentic workflow orchestration&lt;/strong> capabilities of the &lt;a href="https://github.com/google/adk-java">Agent Development Kit&lt;/a> (ADK) for &lt;strong>Java&lt;/strong>. We&amp;rsquo;ve seen how to build robust, specialized AI agents by moving beyond single, monolithic agents. We&amp;rsquo;ve explored how to structure our agents for:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">Flexibility with sub-agents&lt;/a> — Letting an orchestrator LLM decide the best course of action.&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">Order with sequential agents&lt;/a> — Enforcing a strict, predictable execution path.&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;a href="https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/">Efficiency with parallel agents&lt;/a> — Running independent tasks concurrently to save time.&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;a href="https://glaforge.dev/posts/2025/07/28/mastering-agentic-workflows-with-adk-loop-agents/">Refinement with loop agents&lt;/a> — Creating iterative processes for self-correction and complex problem-solving.&lt;/li>
&lt;/ul>
&lt;p>In this final post, let&amp;rsquo;s bring it all together. We&amp;rsquo;ll summarize each pattern, clarify when to use one over the other, and show how their true power is unlocked when you start combining them.&lt;/p></description><content:encoded>
<![CDATA[<p>Over the past few articles, we&rsquo;ve taken a deep dive into the powerful <strong>agentic workflow orchestration</strong> capabilities of the <a href="https://github.com/google/adk-java">Agent Development Kit</a> (ADK) for <strong>Java</strong>. We&rsquo;ve seen how to build robust, specialized AI agents by moving beyond single, monolithic agents. We&rsquo;ve explored how to structure our agents for:</p>
<ul>
<li><strong>Part 1</strong>: <a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">Flexibility with sub-agents</a> — Letting an orchestrator LLM decide the best course of action.</li>
<li><strong>Part 2</strong>: <a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">Order with sequential agents</a> — Enforcing a strict, predictable execution path.</li>
<li><strong>Part 3</strong>: <a href="https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/">Efficiency with parallel agents</a> — Running independent tasks concurrently to save time.</li>
<li><strong>Part 4</strong>: <a href="https://glaforge.dev/posts/2025/07/28/mastering-agentic-workflows-with-adk-loop-agents/">Refinement with loop agents</a> — Creating iterative processes for self-correction and complex problem-solving.</li>
</ul>
<p>In this final post, let&rsquo;s bring it all together. We&rsquo;ll summarize each pattern, clarify when to use one over the other, and show how their true power is unlocked when you start combining them.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>For the impatient</span>
      </div>
      <div class="admonition-content">
        <p>You can also check out this video, generated by my colleague <a href="https://medium.com/@iromin">Romin Irani</a> thanks to the new <strong>Video Overview</strong> feature of <a href="https://notebooklm.google.com/">NotebookLM</a>:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/HqNSvstMlNs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Then dive into the articles to get the full details!</p>
      </div>
    </div><h2 id="a-quick-recap-your-agentic-workflow-cheat-sheet">A quick recap: your agentic workflow cheat sheet</h2>
<p>Choosing the right workflow is about matching the structure of your problem to the structure of your agent system. Here’s a quick guide:</p>
<table>
  <thead>
      <tr>
          <th>Workflow</th>
          <th>Key ADK Class</th>
          <th>Use Case</th>
          <th>Pros</th>
          <th>Cons</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Sub-Agents</strong></td>
          <td><code>LlmAgent</code></td>
          <td>User-driven, flexible tasks where the next step is not always known.</td>
          <td>High flexibility, conversational, great for user-facing bots.</td>
          <td>Less predictable, relies on LLM reasoning for flow control.</td>
      </tr>
      <tr>
          <td><strong>Sequential</strong></td>
          <td><code>SequentialAgent</code></td>
          <td>Fixed, multi-step processes where order is critical.</td>
          <td>Predictable, reliable, easy to debug, guarantees order.</td>
          <td>Inflexible, can be slower if tasks could be parallelized.</td>
      </tr>
      <tr>
          <td><strong>Parallel</strong></td>
          <td><code>ParallelAgent</code></td>
          <td>Gathering data from multiple sources or running independent tasks.</td>
          <td>Highly efficient, significantly reduces latency for I/O-bound tasks.</td>
          <td>All tasks run; no short-circuiting. Less suitable for tasks with dependencies.</td>
      </tr>
      <tr>
          <td><strong>Loop</strong></td>
          <td><code>LoopAgent</code></td>
          <td>Iterative refinement, self-correction, or processes that repeat until a condition is met.</td>
          <td>Powerful for complex problem-solving, enables agents to improve their own work.</td>
          <td>Can lead to infinite loops if not designed carefully (always use <code>maxIterations</code>!).</td>
      </tr>
  </tbody>
</table>
<h2 id="when-to-use-each-workflow">When to use each workflow</h2>
<p>Let&rsquo;s distill the decision-making process down to its essence.</p>
<h3 id="choose-sub-agents-for-flexibility-and-conversation">Choose sub-agents for flexibility and conversation</h3>
<p>The <code>LlmAgent</code> with a team of sub-agents is your go-to for building conversational assistants. This <em>&ldquo;divide and conquer&rdquo;</em> strategy gives an orchestrator LLM the autonomy and agency to choose the right specialist (sub-agent) for the job based on the user&rsquo;s request.</p>
<h4 id="use-it-when">Use it when:</h4>
<ul>
<li>The user is in control, and the conversation can go in many different directions.</li>
<li>You need to delegate to a wide range of tools or specialists.</li>
<li>The exact sequence of operations is not — and should not be — predetermined.</li>
</ul>
<h3 id="choose-sequentialagent-for-order-and-predictability">Choose <code>SequentialAgent</code> for order and predictability</h3>
<p>When you have a process that must follow a specific order, the <code>SequentialAgent</code> is the perfect tool. It creates a fixed pipeline where the output of one step becomes the input for the next. This provides structure and guarantees a consistent outcome.</p>
<h4 id="use-it-when-1">Use it when:</h4>
<ul>
<li>You are automating a business process, like <em>&ldquo;Step A, then Step B, then Step C.&rdquo;</em></li>
<li>The outcome of one agent is a necessary prerequisite for the next.</li>
<li>You need a deterministic and easily debuggable workflow.</li>
</ul>
<h3 id="choose-parallelagent-for-efficiency-and-speed">Choose <code>ParallelAgent</code> for efficiency and speed</h3>
<p>If your workflow involves multiple independent tasks—like fetching data from different APIs or performing separate
analyses—running them sequentially is a waste of time. The <code>ParallelAgent</code> executes these tasks concurrently, dramatically speeding up the total execution time.</p>
<h4 id="use-it-when-2">Use it when:</h4>
<ul>
<li>You have multiple I/O-bound tasks (e.g., web searches, database queries).</li>
<li>The tasks do not depend on each other&rsquo;s results.</li>
<li>Minimizing latency is a primary concern.</li>
</ul>
<h3 id="choose-loopagent-for-iteration-and-refinement">Choose <code>LoopAgent</code> for iteration and refinement</h3>
<p>Some problems can&rsquo;t be solved in a single shot. For tasks that require trial, feedback, and correction — like generating code and then having a reviewer agent critique it — the <code>LoopAgent</code> is indispensable.
It automates the cycle of <em>&ldquo;do, check, refine.&rdquo;</em></p>
<h4 id="use-it-when-3">Use it when:</h4>
<p>An agent needs to improve its work based on feedback (from another agent or a tool).
You are building a system that needs to work towards a goal state through iteration.
The task involves complex problem-solving that benefits from a trial-and-error approach.</p>
<h2 id="the-power-of-composition-better-together">The Power of composition: better together</h2>
<p>While each workflow is powerful on its own, <strong>the real magic happens when you start composing them</strong>.
This is the core philosophy of the ADK: building sophisticated systems from simple, modular blocks.</p>
<p>In our series, we saw a prime example of this with the <code>company-detective</code> agent. It was a <code>SequentialAgent</code> that orchestrated a two-step process:</p>
<ul>
<li>A <code>ParallelAgent</code> that ran three different research agents concurrently (<code>company-profiler</code>, <code>news-finder</code>, <code>financial-analyst</code>).</li>
<li>A final <code>LlmAgent</code> (<code>report-compiler</code>) that took the aggregated results from the parallel step and synthesized them into a final report.</li>
</ul>
<p>This hybrid approach gave us the best of both worlds:
the raw speed of parallel execution for data gathering and the structured order of a sequential pipeline to ensure the final report was only compiled after all the research was complete.</p>
<p>Similarly, we built a code-refiner-assistant by embedding a <code>LoopAgent</code> inside a <code>SequentialAgent</code>.
This allowed us to first iteratively generate and review code until it was perfect, and then proceed to the final step of presenting it to the user.</p>
<h2 id="conclusion-determinism-and-control-for-better-ai">Conclusion: determinism and control for better AI</h2>
<p>By breaking down complex problems and assigning them to specialized agentic workflows, we gain more control and produce more reliable outcomes. Instead of relying on a single, massive LLM to figure everything out, we guide the process.
We trade a little bit of the LLM&rsquo;s raw agency for a massive gain in predictability, maintainability, and overall quality.</p>
<p>The <a href="https://github.com/google/adk-java">ADK for Java</a> gives you the toolkit to be a true AI architect.
By mastering and combining these fundamental patterns (sub-agents, sequential, parallel, and loop) you can move beyond simple bots and start building genuinely capable and robust AI systems that solve real-world problems in a structured and deterministic way.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Mastering agentic workflows with ADK: Loop agents</title><link>https://glaforge.dev/posts/2025/07/28/mastering-agentic-workflows-with-adk-loop-agents/</link><pubDate>Mon, 28 Jul 2025 09:37:02 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/28/mastering-agentic-workflows-with-adk-loop-agents/</guid><description>&lt;p>Welcome to the final installment of our series on &lt;strong>mastering agentic workflows&lt;/strong> with the &lt;a href="https://github.com/google/adk-java">ADK for Java&lt;/a>.
We&amp;rsquo;ve covered a lot of ground:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">Part 1: &lt;strong>Sub-agents&lt;/strong>&lt;/a> for flexible, user-driven delegation.&lt;/li>
&lt;li>&lt;a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">Part 2: &lt;strong>Sequential agents&lt;/strong>&lt;/a> for predictable, ordered processes.&lt;/li>
&lt;li>&lt;a href="https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/">Part 3: &lt;strong>Parallel agents&lt;/strong>&lt;/a> for efficient, concurrent execution.&lt;/li>
&lt;/ul>
&lt;p>Now, we&amp;rsquo;ll explore a pattern that enables agents to mimic a fundamental human problem-solving technique: &lt;strong>iteration&lt;/strong>.
For tasks that require refinement, trial-and-error, and self-correction, the ADK provides a &lt;strong>&lt;code>LoopAgent&lt;/code>&lt;/strong>.&lt;/p></description><content:encoded>
<![CDATA[<p>Welcome to the final installment of our series on <strong>mastering agentic workflows</strong> with the <a href="https://github.com/google/adk-java">ADK for Java</a>.
We&rsquo;ve covered a lot of ground:</p>
<ul>
<li><a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">Part 1: <strong>Sub-agents</strong></a> for flexible, user-driven delegation.</li>
<li><a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">Part 2: <strong>Sequential agents</strong></a> for predictable, ordered processes.</li>
<li><a href="https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/">Part 3: <strong>Parallel agents</strong></a> for efficient, concurrent execution.</li>
</ul>
<p>Now, we&rsquo;ll explore a pattern that enables agents to mimic a fundamental human problem-solving technique: <strong>iteration</strong>.
For tasks that require refinement, trial-and-error, and self-correction, the ADK provides a <strong><code>LoopAgent</code></strong>.</p>
<h2 id="building-agents-that-refine-their-work">Building agents that refine their work</h2>
<p>Think about how you write code. You write a first draft, you review it (or a colleague does), you find issues, and you refine it.
You repeat this cycle until the code is correct. The <code>LoopAgent</code> is designed to automate exactly this kind of iterative process.</p>
<p>A loop workflow is perfect for:</p>
<ul>
<li><strong>Complex problem-solving:</strong> Where the agent needs to try an approach, evaluate the outcome, and then try again.</li>
<li><strong>Self-correction:</strong> Building systems that can review and improve their own output.</li>
<li><strong>Reaching a goal state:</strong> Continuing a process until a specific condition is met.</li>
</ul>
<h2 id="a-practical-example-a-code-refiner-assistant">A practical example: a <code>code-refiner-assistant</code></h2>
<p>Let&rsquo;s look at our example of the day.
I&rsquo;ve built a <code>code-refiner-assistant</code> that iteratively generates and reviews a Python function until it meets the required standards.</p>
<p>Let&rsquo;s detail its components, but first, a visual diagram will help understand the flow:</p>
<p><figure>
  <a href="#img-aaa4f7d983f6538eb20020ba41084eaa">
    <img src="/img/adk/code-refiner-assistant.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-aaa4f7d983f6538eb20020ba41084eaa">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/code-refiner-assistant.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>First, we&rsquo;ll need a <code>code-generator</code> agent:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>codeGenerator<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-generator&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Writes and refines code based on a request and feedback.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to write a Python function
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        based on the user&#39;s request.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        On the first turn, write the initial version of the code.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        On subsequent turns, you will receive feedback on your code.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your task is to refine the code based on this feedback.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Previous feedback (if any):
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {feedback?}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;generated_code&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice two things:</p>
<ul>
<li>The result of this agent will be stored in the <code>generated_code</code> output key (in the agent&rsquo;s state).</li>
<li>As this agent will be part of the refinement loop, it will look at the <code>feedback</code> that it might receive from a previous iteration of the loop. The <code>?</code> character signals the <code>feedback</code> state variable may be absent, which is the case for the first iteration. The question mark indicates that no exception should be thrown if this variable isn&rsquo;t present (again, in the case of the first iteration) this placeholder will just be replaced by an empty string.</li>
</ul>
<p>Next, we need a <code>code-reviewer</code> agent to judge whether the code is complete or needs work:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>codeReviewer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-reviewer&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Reviews code and decides if it&#39;s complete or needs work.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to act as a senior code reviewer.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Analyze the provided Python code for correctness,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        style, and potential bugs.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Code to review:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {generated_code}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;feedback&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>It takes in input the <code>generated_code</code> output from the <code>code-generator</code> agent.
And it will store its result in the <code>feedback</code> state variable, so that the <code>code-generator</code> can update the code if needed.</p>
<p>Now, let&rsquo;s put the two agents in a loop:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>codeRefinerLoop<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LoopAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-refiner-loop&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Iteratively generates and reviews code until it is correct.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>codeGenerator,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>codeReviewer)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxIterations</span>(5)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Safety net!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This coder/refiner loop is the first step of a sequential agent,
and the last step is the following <code>final-presenter</code> agent that displays the resulting reviewed code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>finalPresenter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;final-presenter&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Presents the final, accepted code to the user.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        The code has been successfully generated and reviewed.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Present the final version to the user in a clear format.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Final Code:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {generated_code}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s finalize this setup with the code of the sequential agent, combining the <code>code-refiner-loop</code> and <code>final-presenter</code> agents:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>SequentialAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-refiner-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Manages the full code generation and refinement process.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>codeRefinerLoop,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>finalPresenter)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="the-safety-net-maxiterations">The safety net: <code>maxIterations</code></h2>
<p>If we were running this agent as-is, it could run endlessly, looping over the code generator and code reviewer.
Fortunately, we added the instruction <code>maxIterations(5)</code> to the <code>LoopAgent</code> definition to avoid this situation.
No more than 5 iterations are allowed here.</p>
<p>You should <strong>always specify a maximum number of iterations</strong>, otherwise your agents could be stuck in an endless loop.</p>
<p>Now, we would like to stop the iteration once a certain condition is met:
when we&rsquo;re happy with the quality of the generated code, when the code doesn&rsquo;t need to be further improved.</p>
<h2 id="exiting-the-loop-early-setescalatetrue">Exiting the loop early: <code>setEscalate(true)</code></h2>
<p>An uncontrolled loop is a dangerous thing. To stop a <code>LoopAgent</code>, there is one fundamental mechanism: calling <code>setEscalate(true)</code> on the context&rsquo;s event actions.
This is the universal <em>&ldquo;break&rdquo;</em> statement that tells the ADK runtime to stop the current agent and pass control to its parent.</p>
<p>The key to building robust loops is understanding the two main strategies for invoking this critical method.</p>
<h3 id="strategy-1-in-flight-escalation-with-a-functiontool">Strategy #1: in-flight escalation with a <code>FunctionTool</code></h3>
<p>This approach treats exiting the loop as a primary, explicit action the agent must perform.</p>
<p>First, we define a simple Java method in our class to serve as the tool&rsquo;s implementation.
This method does one powerful thing: it calls <code>setEscalate(true)</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Schema</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Call this function ONLY when the code-reviewer
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    agent indicates no further changes are needed,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    signaling the iterative process should end.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">exitLoop</span>(ToolContext<span style="color:#bbb"> </span>toolContext)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>toolContext.<span style="color:#4070a0">eventActions</span>().<span style="color:#4070a0">setEscalate</span>(<span style="color:#007020;font-weight:bold">true</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>();<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Return value can an empty map</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next, we create an instance of <code>FunctionTool</code> and register it with our <code>code-reviewer</code> agent.
We also instruct the agent that it <strong>MUST</strong> call this tool when it&rsquo;s satisfied.</p>
<p>Let&rsquo;s have a look at the modified <code>code-reviewer</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>codeReviewer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-reviewer&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Reviews code and decides if it&#39;s complete
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        or needs more work.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to act as a senior code reviewer.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Analyze the provided Python code for correctness,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        style, and potential bugs.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Code to review:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {generated_code}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        If the code is perfect and meets the user&#39;s request,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you MUST call the `exitLoop` tool.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Otherwise, provide constructive feedback
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        for the `code-generator` to improve the code.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;feedback&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(FunctionTool.<span style="color:#4070a0">create</span>(<span style="color:#007020;font-weight:bold">this</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;exitLoop&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>When the agent calls this tool, the escalation happens <em>during</em> the agent&rsquo;s turn, interrupting the normal flow.
This pattern is powerful because the decision to exit is a deliberate, observable action in the agent&rsquo;s execution trace.</p>
<h3 id="strategy-2-programmatic-escalation-via-callbacks">Strategy #2: programmatic escalation via <code>Callback</code>s</h3>
<p>This approach is more programmatic.
Instead of a tool, we instruct the <code>code-reviewer</code> to output a simple keyword (like &ldquo;EXIT&rdquo;) when it&rsquo;s done.
Then, we use a callback on the following agent to check for this keyword.</p>
<p>However, there&rsquo;s an important subtlety: a callback runs <em>before</em> or <em>after</em> an agent&rsquo;s turn starts or is complete.
Simply calling <code>setEscalate(true)</code> inside a callback is not enough, because the runtime might not <em>&ldquo;notice&rdquo;</em> it.</p>
<p><strong>To ensure the escalation is processed</strong>, the callback must also do one of two things:</p>
<ol>
<li><strong>Modify the state</strong> (so the state delta is not empty).</li>
<li><strong>Return a non-empty <code>Maybe</code></strong> (e.g., <code>Maybe.just(...)</code>).</li>
</ol>
<p>This signals to the runtime that something has changed, prompting it to check for the escalation flag.
Returning <code>Maybe.empty()</code> in a callback is equivalent to actually doing nothing!</p>
<p>For callbacks, there are two approaches: we can draw a parallel to classic programming loops: <code>do/while</code> and <code>while</code>,
depending on whether you want to check before or after an agent runs to exit the loop.</p>
<p><figure>
  <a href="#img-b8a6c307949d194e1a4fb5cfbfea5441">
    <img src="/img/adk/adk-before-after-agent-callback.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b8a6c307949d194e1a4fb5cfbfea5441">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/adk-before-after-agent-callback.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h4 id="the-dowhile-approach-afteragentcallback">The <code>do/while</code> approach: <code>afterAgentCallback</code></h4>
<p>This pattern checks the exit condition <em>after</em> the main work of the iteration is done.
We place an <code>afterAgentCallback</code> usually on the <em>last</em> agent in the loop (<code>code-reviewer</code>).
But you could also decide to exit the loop mid-way as well, depending on your use case.</p>
<p>It&rsquo;s important to also tweak the instructions to force the agent to return a particular magic word,
so that the callback can check the result and decide whether to exit.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>codeReviewer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-reviewer&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Reviews code and decides if it&#39;s complete or needs more work.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to act as a senior code reviewer.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Analyze the provided Python code for correctness,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        style, and potential bugs.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Code to review:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {generated_code}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        If the code is perfect and meets the user&#39;s request,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you MUST reply with just one single word: EXIT
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t add any introduction or commentary.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Just reply with EXIT.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Otherwise, provide constructive feedback
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        for the `code-generator to improve the code.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;feedback&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">afterAgentCallback</span>(callbackContext<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>feedback<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>callbackContext.<span style="color:#4070a0">state</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">getOrDefault</span>(<span style="color:#4070a0">&#34;feedback&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span>).<span style="color:#4070a0">toString</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(feedback.<span style="color:#4070a0">trim</span>().<span style="color:#4070a0">equalsIgnoreCase</span>(<span style="color:#4070a0">&#34;EXIT&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>callbackContext.<span style="color:#4070a0">eventActions</span>().<span style="color:#4070a0">setEscalate</span>(<span style="color:#007020;font-weight:bold">true</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>callbackContext.<span style="color:#4070a0">state</span>().<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;review&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;OK&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">just</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(<span style="color:#4070a0">&#34;EXIT&#34;</span>)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">empty</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>})<span style="color:#bbb">
</span></span></span></code></pre></div><p>The loop body (both <code>code-generator</code> and <code>code-reviewer</code>) always runs.
Then, the callback inspects the result.
Because the state is changed with a new <code>review</code> state variable,
and because we return a non-empty <code>Maybe</code>,
the runtime will process the <code>setEscalate(true)</code> flag and stop the next iteration.</p>
<p>Note that only one of setting a new state variable, and returning a non-empty <code>Maybe</code> is enough:
no need to do both like in this example.</p>
<h4 id="the-while-approach-beforeagentcallback">The <code>while</code> approach: <code>beforeAgentCallback</code></h4>
<p>This pattern checks the exit condition <em>before</em> the iteration begins.
We place a <code>beforeAgentCallback</code> on the <em>first</em> agent in the loop (<code>code-generator</code>).
So the idea is to check whether a condition is met before starting a new iteration.</p>
<p>In our example, that applies to the <code>code-generator</code>,
but there&rsquo;s no need to change the prompt this time,
only adding the <code>beforeAgentCallback</code> is needed:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>codeGenerator<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;code-generator&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Writes and refines code based on a request and feedback.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to write a Python function
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        based on the user&#39;s request.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        On the first turn, write the initial version of the code.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        On subsequent turns, you will receive feedback on your code.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your task is to refine the code based on this feedback.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Previous feedback (if any):
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {feedback?}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;generated_code&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">beforeAgentCallback</span>(callbackContext<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>feedback<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>callbackContext.<span style="color:#4070a0">state</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">getOrDefault</span>(<span style="color:#4070a0">&#34;feedback&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span>).<span style="color:#4070a0">toString</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(feedback.<span style="color:#4070a0">trim</span>().<span style="color:#4070a0">equalsIgnoreCase</span>(<span style="color:#4070a0">&#34;EXIT&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>callbackContext.<span style="color:#4070a0">eventActions</span>().<span style="color:#4070a0">setEscalate</span>(<span style="color:#007020;font-weight:bold">true</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">just</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(<span style="color:#4070a0">&#34;EXIT&#34;</span>)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">empty</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is highly efficient. It checks the result from the last turn, and if it&rsquo;s time to exit,
it escalates <em>before</em> running the <code>code-generator</code>, saving an unnecessary LLM call.</p>
<h3 id="which-strategy-is-better">Which strategy is better?</h3>
<p>There&rsquo;s no single answer.</p>
<ul>
<li>The <strong><code>FunctionTool</code></strong> approach is excellent when the decision to exit is a core part of the agent&rsquo;s responsibility and should be an explicit action.</li>
<li>The <strong>Callback</strong> approach offers a more deterministic, programmatic check that is less reliant on the LLM&rsquo;s tool-calling ability, making it ideal for simple state-based exit conditions.</li>
</ul>
<h3 id="dont-forget-the-safety-net">Don&rsquo;t forget the safety net!</h3>
<p>Regardless of the pattern you choose, you must always include a <code>maxIterations</code> limit.
This is a critical safety net to prevent infinite loops if the agents get stuck in a cycle of corrections.</p>
<h2 id="conclusion-the-power-of-composition">Conclusion: The power of composition</h2>
<p>Across this four-part series, we&rsquo;ve seen how the ADK for Java provides a powerful and elegant toolkit for building sophisticated AI systems.
We&rsquo;ve moved beyond single, monolithic agents to composing workflows that are flexible, ordered, efficient, and even iterative.</p>
<ul>
<li><strong>Sub-agents</strong> brings flexibility and grants agents agency.</li>
<li><strong>Sequential agents</strong> give us order.</li>
<li><strong>Parallel agents</strong> give us speed.</li>
<li><strong>Loop agents</strong> give us intelligence through iteration.</li>
</ul>
<p>The real magic happens when you realize these aren&rsquo;t mutually exclusive choices.
They are composable building blocks.
By combining these patterns, you can design and build truly advanced agentic applications that can tackle complex, multi-step problems in a robust and maintainable way.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Mastering agentic workflows with ADK for Java: Parallel agents</title><link>https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/</link><pubDate>Fri, 25 Jul 2025 10:20:33 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/25/mastering-agentic-workflows-with-adk-parallel-agent/</guid><description>&lt;p>Let&amp;rsquo;s continue our exploration of &lt;a href="https://github.com/google/adk-java">ADK&lt;/a> for Java (Agent Development Kit for building AI agents).
In this series, we&amp;rsquo;ve explored two fundamental agentic workflows:&lt;/p>
&lt;ul>
&lt;li>First, we used &lt;a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">&lt;code>LlmAgent&lt;/code> with sub-agents&lt;/a> to create a flexible, manager-and-specialists model.&lt;/li>
&lt;li>Then, we used the &lt;a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/">&lt;code>SequentialAgent&lt;/code>&lt;/a> to enforce a strict, linear order of operations.&lt;/li>
&lt;/ul>
&lt;p>But what if your problem isn&amp;rsquo;t about flexibility or a fixed sequence? What if it&amp;rsquo;s about &lt;strong>efficiency&lt;/strong>? Some tasks don&amp;rsquo;t depend on each other and can be done at the same time. Why wait for one to finish before starting the next?&lt;/p></description><content:encoded>
<![CDATA[<p>Let&rsquo;s continue our exploration of <a href="https://github.com/google/adk-java">ADK</a> for Java (Agent Development Kit for building AI agents).
In this series, we&rsquo;ve explored two fundamental agentic workflows:</p>
<ul>
<li>First, we used <a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/"><code>LlmAgent</code> with sub-agents</a> to create a flexible, manager-and-specialists model.</li>
<li>Then, we used the <a href="https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/"><code>SequentialAgent</code></a> to enforce a strict, linear order of operations.</li>
</ul>
<p>But what if your problem isn&rsquo;t about flexibility or a fixed sequence? What if it&rsquo;s about <strong>efficiency</strong>? Some tasks don&rsquo;t depend on each other and can be done at the same time. Why wait for one to finish before starting the next?</p>
<p>This is where the ADK for Java offers a third, powerful pattern: the <strong><code>ParallelAgent</code></strong>.</p>
<h2 id="maximum-efficiency-with-parallel-workflows">Maximum efficiency with parallel workflows</h2>
<p>A parallel workflow is designed to execute multiple agents concurrently. It&rsquo;s the perfect solution when you need to perform several independent tasks and want to get them done as quickly as possible. The <code>ParallelAgent</code> runs its sub-agents in separate threads, waits for all of them to complete, and then gathers the results.</p>
<p>This is ideal for:</p>
<ul>
<li><strong>Comprehensive data gathering:</strong> Running different types of searches or analyses on the same topic simultaneously.</li>
<li><strong>Independent sub-tasks:</strong> Performing unrelated actions that can happen in any order.</li>
<li><strong>Saving time:</strong> Drastically reducing the total execution time compared to running the same tasks one after another.</li>
</ul>
<h2 id="a-practical-example-the-market-researcher">A practical example: the <code>market-researcher</code></h2>
<p>Let&rsquo;s look at our <code>ParallelFlow.java</code> example, illustrated by the below diagram:</p>
<p><figure>
  <a href="#img-07630358efad7de7d44ddc88928f5c51">
    <img src="/img/adk/financial-report.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-07630358efad7de7d44ddc88928f5c51">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/financial-report.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Here&rsquo;s the full source code, but we&rsquo;ll explore each part in details further down:</p>

<details>
  <summary>Click to see the full source code, before diving in</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>companyProfiler<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;company-profiler&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Provides a general overview of a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to provide a brief overview of the
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        given company.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Include its mission, headquarters, and current CEO.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the Google Search Tool to find this information.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;profile&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>newsFinder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;news-finder&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Finds the latest news about a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to find the top 3-4 recent news headlines
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        for the given company.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the Google Search Tool.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Present the results as a simple bulleted list.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;news&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>financialAnalyst<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;financial-analyst&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Analyzes the financial performance of a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to provide a snapshot of the given company&#39;s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        recent financial performance.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Focus on stock trends or recent earnings reports.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the Google Search Tool.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;financials&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>marketResearcher<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ParallelAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;market-researcher&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Performs comprehensive market research on a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>companyProfiler,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>newsFinder,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>financialAnalyst<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>reportCompiler<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;report-compiler&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Compiles a final market research report.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to synthesize the provided information
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        into a coherent market research report.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Combine the company profile, latest news, and
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        financial analysis into a single, well-formatted report.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Company Profile
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {profile}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Latest News
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {news}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Financial Snapshot
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {financials}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>SequentialAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;company-detective&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Collects various market information about a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>marketResearcher,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>reportCompiler<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<p>Now let&rsquo;s zoom in.
We&rsquo;ve built a <code>market-researcher</code> designed to quickly compile a report on a public company.
The research involves several distinct tasks that don&rsquo;t depend on each other:</p>
<ul>
<li><strong><code>company-profiler</code></strong>: Finds the company&rsquo;s mission, CEO, headquarter, etc.</li>
<li><strong><code>news-finder</code></strong>: Scans for recent news headlines.</li>
<li><strong><code>financial-analyst</code></strong>: Looks up stock performance.</li>
</ul>
<p>Those agents are taking advantage of the <code>GoogleSearchTool</code> to find up-to-date information, beyond LLM&rsquo;s training cut-off date.</p>
<p>We can run all three of these at the same time. Each agent is configured with an <code>outputKey</code> to store its findings, just like we saw with the <code>SequentialAgent</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>companyProfiler<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;company-profiler&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;profile&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>newsFinder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;news-finder&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;news&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>financialAnalyst<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;financial-analyst&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;financials&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="the-best-of-both-worlds-a-hybrid-sequential-parallel-flow">The best of both worlds: a hybrid sequential-parallel flow</h2>
<p>Now, here&rsquo;s where we take our architecture to the next level.
We&rsquo;re composing different flows, by <strong>embedding our <code>ParallelAgent</code> inside a <code>SequentialAgent</code></strong>.</p>
<p>This creates a multi-stage pipeline that combines the efficiency of parallel execution with the structured order of a sequence.</p>
<p>Here&rsquo;s how our hybrid <code>company-detective</code> works.
It&rsquo;s a <code>SequentialAgent</code> agent.
It will trigger the parallel flow, and once it completes, it will compile the final report from the output of the parallel agents.</p>
<p>The sequential agent combines two steps:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>SequentialAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;company-detective&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Collects various market information about a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>marketResearcher,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>reportCompiler<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><ol>
<li>
<p><strong>Step 1 (in parallel):</strong> Sub-agents are collecting information in parallel about the company, the latest news, and its financial status.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>marketResearcher<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ParallelAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;market-researcher&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Performs comprehensive market research on a company.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>companyProfiler,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>newsFinder,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>financialAnalyst<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This agent&rsquo;s only job is to execute the three research tasks concurrently.
Once all three are done, it bundles their outputs (<code>profile</code>, <code>news</code>, <code>financials</code>) and passes them to the next stage in the sequence, thanks to <code>outputKey</code>s.</p>
</li>
<li>
<p><strong>Step 2 (sequentially):</strong> A final <code>report-compiler</code> agent runs.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>reportCompiler<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;report-compiler&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to synthesize the provided information...
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Company Profile
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {profile}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Latest News
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {news}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ## Financial Snapshot
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {financials}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This agent is a standard step in our <code>SequentialAgent</code>. It takes the collected data from the completed parallel step and formats it into a clean, final report.</p>
</li>
</ol>
<p>By structuring our flow this way, we&rsquo;ve created a highly modular and extensible pipeline. The parallel research block is a self-contained, reusable component. We could easily add more sequential steps before or after it, like a step to verify the company&rsquo;s stock ticker first, or a final step to email the report.</p>
<h2 id="example-run">Example run</h2>
<p>I asked my composite agent: <code>Give me a report about Google</code>.
It ran through the various agents, and generated the following report about the company:</p>

<details>
  <summary>Click to read the full report</summary>
  <pre tabindex="0"><code>## Google: Market Research Report (Q2 2025)

Executive Summary:

This report provides an overview of Google, its recent performance,
and current market position. Google, a subsidiary of Alphabet Inc.,
continues to be a dominant force in the technology industry, driven
by its core search engine, cloud services, and advancements in
artificial intelligence. Recent financial results for Q2 2025
exceeded expectations, demonstrating strong growth across key
segments. However, Google faces ongoing antitrust scrutiny and
reputational challenges.

1. Company Profile:

*   Name: Google (a subsidiary of Alphabet Inc.)
*   Mission: To organize the world&#39;s information and make it
    universally accessible and useful.
*   Headquarters: 1600 Amphitheatre Parkway, Mountain View,
    California (Googleplex)
*   CEO: Sundar Pichai (also CEO of Alphabet Inc.)

2. Latest News &amp; Developments:

*   Antitrust Lawsuit: Illinois has joined an antitrust lawsuit
    accusing Google of monopolizing advertising technology, adding
    to the legal challenges faced by the company.
*   Antitrust Violation: A federal judge ruled that Google violated
    antitrust laws, further highlighting regulatory pressures.
*   Google Maps Update: Google Maps is now displaying the Gulf of
    Mexico as the &#34;Gulf of America&#34; for U.S. users, sparking some
    controversy and discussion.
*   BadBox 2.0 Botnet: Google is taking legal action against the
    BadBox 2.0 botnet, demonstrating its commitment to cybersecurity
    and protecting its users.

3. Financial Analysis (Q2 2025):

Alphabet Inc. reported strong financial results for the second
quarter of 2025, exceeding analysts&#39; expectations.

*   Revenue: Consolidated revenue increased by 14% year-over-year
    to $96.4 billion (13% increase in constant currency).
    *   Google Services revenue increased by 12% to $82.5 billion,
        driven by Google Search, YouTube ads, and Google
        subscriptions.
    *   Google Cloud revenue increased by 32% to $13.6 billion,
        fueled by growth in Google Cloud Platform (GCP), AI
        Infrastructure, and Generative AI Solutions. The annual
        revenue run-rate for Google Cloud now exceeds $50 billion.
*   Operating Income: Increased by 14%, with an operating margin
    of 32.4%.
*   Net Income: Increased by 19%, and Earnings Per Share (EPS)
    increased by 22% to $2.31.
*   Capital Expenditures: Increased investment to approximately
    $85 billion in 2025 due to strong demand for cloud products
    and services.

4. Stock Performance &amp; Trends:

*   Stock Performance: Alphabet&#39;s Class A shares initially fell
    slightly in after-hours trading but then rose by 2%.
    The stock was up about 1% for 2025 through Wednesday&#39;s close.
*   Analyst Ratings: The average rating for GOOG stock is &#34;Buy,&#34;
    with a 12-month stock price target of $206.35, representing a
    potential increase of 7.75%.
*   Recent Stock Movement: GOOG stock has risen by 3.96% compared
    to the previous week and 14.52% over the last month.
*   52-Week Range: $140.53 (low) to $207.05 (high).
*   AI Impact: Google&#39;s AI Overview tool has over 2 billion monthly
    users, boosting search impressions by 49% since launch.
*   Long-Term Growth: An investment of $1,000 in Alphabet stock at
    its IPO in 2004 would be worth approximately $75,272 today,
    reflecting a compound annual growth rate of 22.92% over 21 years.

5. Key Takeaways &amp; Future Outlook:

*   AI as a Growth Driver: Sundar Pichai emphasizes that AI is
    positively impacting every part of the business and driving
    strong momentum. Google&#39;s advancements in AI are expected to
    continue to be a significant growth driver.
*   Cloud Services Expansion: The strong growth in Google Cloud
    revenue signifies its increasing competitiveness in the cloud
    computing market, challenging leaders like AWS and Azure.
    Increased capital expenditure in this area further indicates
    Google&#39;s commitment to expanding its cloud infrastructure.
*   Financial Strength: Alphabet&#39;s Q2 2025 results demonstrate
    its robust financial health and ability to generate significant
    revenue and profit.
*   Potential Challenges: Ongoing antitrust lawsuits and scrutiny
    pose risks to Google&#39;s market dominance and could potentially
    lead to changes in its business practices. Public perception
    issues, such as those arising from the Google Maps update,
    require careful management.

6. Conclusion:

Google remains a powerful and influential company with a strong
financial foundation and a commitment to innovation. Its investments
in AI and cloud computing position it well for future growth.
However, the company must navigate regulatory challenges and manage
its public image effectively to maintain its market leadership.
The overall outlook for Google appears positive, contingent on
its ability to adapt to evolving market dynamics and regulatory
pressures.
</code></pre></details>

<h2 id="choosing-the-right-workflow">Choosing the right workflow</h2>
<p>Let&rsquo;s quickly recap the three patterns we&rsquo;ve covered:</p>
<ul>
<li><strong><code>LlmAgent</code> with sub-agents:</strong> Choose this for <strong>flexibility</strong>. The orchestrator LLM decides the best next step. It&rsquo;s great for conversational bots and user-driven tasks.</li>
<li><strong><code>SequentialAgent</code>:</strong> Choose this for <strong>order</strong>. The process is fixed and predictable. It&rsquo;s perfect for automating linear, multi-step procedures.</li>
<li><strong><code>ParallelAgent</code>:</strong> Choose this for <strong>efficiency</strong>. Independent tasks run concurrently. It&rsquo;s ideal for data gathering and speeding up workflows.</li>
</ul>
<p>And as we&rsquo;ve just seen, the true power comes from <strong>composing these patterns together</strong> to build sophisticated, real-world applications.</p>
<p>In the final article of this series, we&rsquo;ll explore one more fascinating workflow: the <strong>loop flow</strong>, designed for tasks that require iteration and self-correction.
Stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Mastering agentic workflows with ADK for Java: Sequential agents</title><link>https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/</link><pubDate>Thu, 24 Jul 2025 08:58:00 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/24/mastering-agentic-workflows-with-adk-sequential-agent/</guid><description>&lt;p>In the &lt;a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">first part of this series&lt;/a>, we explored the &lt;em>&amp;ldquo;divide and conquer&amp;rdquo;&lt;/em> strategy using &lt;strong>sub-agents&lt;/strong> to create a flexible, modular team of AI specialists. This is perfect for situations where the user is in the driver&amp;rsquo;s seat, directing the flow of conversation. But what about when the &lt;em>process&lt;/em> itself needs to be in charge?&lt;/p>
&lt;p>Some tasks are inherently linear. You have to do Step A before Step B, and Step B before Step C. Think about a CI/CD pipeline: you build, then you test, then you deploy. You can&amp;rsquo;t do it out of order&amp;hellip; or if you do, be prepared for havoc!&lt;/p></description><content:encoded>
<![CDATA[<p>In the <a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">first part of this series</a>, we explored the <em>&ldquo;divide and conquer&rdquo;</em> strategy using <strong>sub-agents</strong> to create a flexible, modular team of AI specialists. This is perfect for situations where the user is in the driver&rsquo;s seat, directing the flow of conversation. But what about when the <em>process</em> itself needs to be in charge?</p>
<p>Some tasks are inherently linear. You have to do Step A before Step B, and Step B before Step C. Think about a CI/CD pipeline: you build, then you test, then you deploy. You can&rsquo;t do it out of order&hellip; or if you do, be prepared for havoc!</p>
<p>For these situations, the <a href="https://github.com/google/adk-java">ADK for Java</a> provides another powerful tool in our orchestration toolbox: the <strong><code>SequentialAgent</code></strong>.</p>
<h2 id="when-order-matters-the-sequential-workflow">When order matters: the sequential workflow</h2>
<p>A sequential workflow is all about creating a predictable, step-by-step process. Unlike the flexible sub-agent model where an orchestrator LLM decides which sub-agent to invoke and when, a <code>SequentialAgent</code> executes a predefined list of agents in a fixed order.</p>
<p>This is useful for:</p>
<ul>
<li><strong>Automating multi-step processes:</strong> Guiding a user through a fixed procedure, like a trip planner or a complex data analysis pipeline.</li>
<li><strong>Ensuring consistency:</strong> Guaranteeing that tasks are always performed in the correct sequence for reliable and predictable outcomes.</li>
<li><strong>Building on previous results:</strong> Creating workflows where the output of one step is the essential input for the next.</li>
</ul>
<h2 id="a-practical-example-a-trip-planner">A practical example: a <code>trip-planner</code></h2>
<p>Let&rsquo;s dive into our <code>SequentialFlow.java</code> example (code further down). This code defines a <code>trip-planner</code> agent that guides a user through the process of planning a vacation. You can&rsquo;t suggest restaurants before you have an itinerary, and you can&rsquo;t build an itinerary before you&rsquo;ve researched the destination. This is a perfect use case for a <code>SequentialAgent</code>.</p>
<p>Our <code>trip-planner</code> consists of three specialist agents that will be executed in a specific order:</p>
<ol>
<li><strong><code>destination-researcher</code></strong>: The first step. It takes a user&rsquo;s request (e.g., <em>&ldquo;a romantic weekend in Paris&rdquo;</em>) and finds suitable attractions.</li>
<li><strong><code>itinerary-creator</code></strong>: The second step. It takes the list of attractions from the previous step and organizes it into a logical 2-day plan.</li>
<li><strong><code>restaurant-suggester</code></strong>: The final step. It takes the completed itinerary and enriches it with relevant lunch and dinner recommendations.</li>
</ol>
<p>I&rsquo;ll show you the full source code here first, but then in subsequent sections, we&rsquo;ll explore each steps in more detail.</p>

<details>
  <summary>Click to see the full source code, before diving in</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>destinationResearcher<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;destination-researcher&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Finds points of interest for a given destination
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and travel style.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to find points of interest for a given
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        city and travel style, according to the duration of
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        the trip, and potentially budget.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the Google Search Tool to find relevant information.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Present the results as a simple bullet point list
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        of key attractions.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;destination-research&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>itineraryCreator<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;itinerary-creator&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Creates an itinerary from a list of attractions.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to create a logical itinerary
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        from the provided list of attractions:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {destination-research}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Group the attractions by proximity or theme for each day.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Present the itinerary clearly, with each day&#39;s plan laid out.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;itinerary&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>restaurantSuggester<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;restaurant-suggester&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Suggests restaurants for each day of the itinerary.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to suggest one lunch and one dinner restaurant
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        for each day of the itinerary:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {itinerary}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the Google Search Tool to find restaurants
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        that are near the day&#39;s activities
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and match the overall travel style.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Add the restaurant suggestions to the itinerary.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>SequentialAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;trip-planner&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Helps you plan a trip by finding attractions,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        creating an itinerary, and suggesting restaurants.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>destinationResearcher,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>itineraryCreator,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>restaurantSuggester<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<h2 id="the-magic--of-state-passing-outputkey">The magic ✨ of state passing: <code>outputKey</code></h2>
<p><em>&ldquo;But wait,&rdquo;</em> you ask, <em>&ldquo;if these are separate agents, how does the <code>itinerary-creator</code> know what the <code>destination-researcher</code> found?&rdquo;</em></p>
<p>This is the core mechanism of the <code>SequentialAgent</code>: <strong>passing state between agents</strong>. The ADK makes this incredibly simple and elegant using an <code>outputKey</code>.</p>
<p>Here’s how it works:</p>
<ol>
<li>
<p><strong>The <em>producer</em> agent:</strong> The agent that generates the data uses the <code>.outputKey(&quot;some-key&quot;)</code> method. This tells the <code>SequentialAgent</code> to store the final result of this agent&rsquo;s turn in a context variable named <code>some-key</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>destinationResearcher<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;destination-researcher&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... instructions ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;destination-research&#34;</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// &lt;-- Produces the data</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div></li>
<li>
<p><strong>The <em>consumer</em> agent:</strong> The next agent in the sequence can then reference this stored data directly in its instructions using curly braces: <code>{some-key}</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>itineraryCreator<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;itinerary-creator&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to create a logical itinerary
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        from the provided list of attractions:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {destination-research} // &lt;-- Consumes the data
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Group the attractions by proximity or theme for each day.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputKey</span>(<span style="color:#4070a0">&#34;itinerary&#34;</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Can produce data for the next agent!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div></li>
</ol>
<p>The ADK handles the injection automatically. The <code>{destination-research}</code> placeholder will be replaced by the full output of the <code>destinationResearcher</code> agent before the prompt is sent to the <code>itineraryCreator</code> agent.</p>
<h2 id="building-the-sequentialagent">Building the <code>SequentialAgent</code></h2>
<p>Putting it all together is straightforward. We use the <code>SequentialAgent.builder()</code> and provide our agents to the <code>.subAgents()</code> method.
The key difference here is that <strong>the order is strictly enforced</strong>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>SequentialAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;trip-planner&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Helps you plan a trip by finding attractions,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        creating an itinerary, and suggesting restaurants.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>destinationResearcher,<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// will always run first</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>itineraryCreator,<span style="color:#bbb">      </span><span style="color:#60a0b0;font-style:italic">// will always run second</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>restaurantSuggester<span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// will always run third</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>When this agent runs, it will execute <code>destinationResearcher</code>, take its output, feed it into <code>itineraryCreator</code>, take <em>its</em> output, feed it into <code>restaurantSuggester</code>, and finally return the completed, enriched itinerary to the user.
The last agent could also reference the output of the first agent if needed, for full context awareness!</p>
<h2 id="example-run">Example run</h2>

<details>
  <summary>Click to see the the output of a full run</summary>
  <p>Ideally, you would have a first agent collecting key information like the destination, number of days of the stay, the budget you plan to spend, the types of attractions you&rsquo;d like to visit, and the sort of cuisine you want to eat.
Here, I&rsquo;m going straight with the full detailed request:</p>
<pre tabindex="0"><code>👤 You &gt; Plan a trip of 5 days in Paris, with a medium budget,
to visit the key tourist attractions (museums, parks, landmarks),
and eat French cuisine!
</code></pre><p>Let&rsquo;s see what the destination research replies:</p>
<pre tabindex="0"><code>🤖 destination-researcher &gt;

Okay, I can help you plan a 5-day trip to Paris focusing on key
tourist attractions, French cuisine, and keeping it within a medium
budget. Here are some ideas:

Here is a possible itinerary for your 5-day trip to Paris:

Attractions:

*   Eiffel Tower: A must-see iconic landmark. Consider booking timed
    tickets in advance to avoid long queues. You can climb to the
    second floor or the top for panoramic views.
*   Louvre Museum: One of the world&#39;s largest and most visited
    museums, home to masterpieces like the Mona Lisa. Booking a time
    slot in advance is essential.
*   Arc de Triomphe: Climb to the top of the Arc for stunning views
    of the Champs-Élysées and the city.
*   Notre Dame Cathedral: While still undergoing restoration, you can
    view the cathedral from the outside and visit the archeological
    crypt.
*   Seine River Cruise: A relaxing way to see many of Paris&#39;s famous
    landmarks from a different perspective. Many cruises depart
    near the Eiffel Tower.
*   Musée d&#39;Orsay: Housed in a former railway station, this museum
    features Impressionist and Post-Impressionist art.
*   Palace of Versailles: A former royal residence, this opulent
    palace and its gardens are a must-see. Advance booking is required.
*   Sainte-Chapelle: Known for its stunning stained glass windows.
    Booking a timeslot in advance is recommended.
*   Montmartre &amp; Sacré-Cœur Basilica: Explore the artistic neighborhood
    of Montmartre and visit the Sacré-Cœur Basilica for panoramic views.

Parks &amp; Gardens:

*   Luxembourg Gardens: A beautiful and popular park with gardens,
    fountains, and activities.
*   Tuileries Garden: Located between the Louvre and Place de la
    Concorde, this formal garden is perfect for a stroll.
*   Parc des Buttes-Chaumont: One of the most beautiful and unique
    gardens in Paris, with an artificial mountain structure and
    impressive panoramic views.
*   Parc Monceau: An English-style garden with follies, statues,
    and children&#39;s play areas.

Food:

*   Bouillon Chartier: Offers traditional French cuisine
    at affordable prices.
*   Chez Gladines: Serves hearty Basque-inspired cuisine
    at reasonable prices.
*   Le Bistrot des Vosges: A no-frills restaurant with classic
    French dishes at affordable prices.
*   Brasserie Dubillot: An all-day spot for affordable
    French classics.
*   Explore local markets: Visit Rue Montorgueil
    for a taste of local market life.

Possible Itinerary Outline:

*   Day 1: Eiffel Tower, Champ de Mars, Seine River cruise,
           Arc de Triomphe, Champs-Élysées.
*   Day 2: Louvre Museum, Tuileries Garden, Place de la Concorde,
           Sainte-Chapelle, Notre Dame (exterior).
*   Day 3: Montmartre (Sacré-Cœur Basilica, Place du Tertre),
           Moulin Rouge (exterior), explore the neighborhood.
*   Day 4: Versailles Palace and Gardens (allow a full day).
*   Day 5: Musée d&#39;Orsay, Luxembourg Gardens,
           explore the Latin Quarter.

Tips for a Medium Budget:

*   Paris Museum Pass: Consider purchasing a Paris Museum Pass
    for access to over 50 museums and monuments.
    A 4-day pass might be a good option.
*   Free museum days: Some museums offer free entry on the first
    Sunday of the month, but be prepared for crowds.
*   Lunch menus: Many restaurants offer affordable fixed-price
    lunch menus.
*   Picnics: Enjoy picnics in the parks and gardens
    to save money on meals.
*   Transportation: Use the efficient
    and affordable Paris Metro system.
*   Accommodation: Consider staying in areas slightly outside
    the city center for more affordable options.
*   Look for &#34;bouillons&#34;: These restaurants serve traditional
    French food at reasonable prices.

Enjoy your trip to Paris!
</code></pre><p>It found some good suggestions for the duration of the trip, and does so considering the allocated budget.</p>
<p>Now let&rsquo;s see what the itinerary agent comes up with:</p>
<pre tabindex="0"><code>🤖 itineraryCreator &gt;

Okay, here is a possible 5-day Paris itinerary with a medium budget,
focusing on key attractions, French cuisine,
and efficient use of time:

Day 1: Iconic Paris &amp; Latin Quarter Charm

*   Morning (9:00 AM): Eiffel Tower. Pre-book tickets online to
    avoid long queues. Ascend to the second floor for stunning
    panoramic views. Budget ~€18-€29 (depending on lift access).
*   Lunch (12:00 PM): Enjoy a classic French crepe from a street
    vendor near the Eiffel Tower. Budget ~€8-€12.
*   Afternoon (1:30 PM): Champ de Mars &amp; Musée du Quai Branly -
    Jacques Chirac. Relax in the park with views of the tower.
    Then visit the museum dedicated to non-European art and cultures.
    Museum Budget: ~€12.
*   Late Afternoon (4:30 PM): Walk or take the Metro to the
    Latin Quarter. Explore the historic Sorbonne University and
    browse the Shakespeare and Company bookstore.
    Dinner (7:30 PM): Enjoy a traditional French meal at a bistro
    in the Latin Quarter. Look for set menus (formules) for better
    value. Try a steak frites or moules marinières*. Budget ~€20-€30.

Day 2: Art, Gardens &amp; Montmartre Magic

*   Morning (9:00 AM): Musée du Louvre. Focus on key masterpieces
    like the Mona Lisa, Venus de Milo, and Winged Victory
    of Samothrace. Pre-book tickets! Budget: ~€17.
    Lunch (12:30 PM): Grab a quick and tasty sandwich jambon-beurre
    (ham and butter sandwich) from a boulangerie* near the Louvre.
    Budget ~€5-€8.
*   Afternoon (2:00 PM): Tuileries Garden. Stroll through the
    beautiful gardens connecting the Louvre to Place de la Concorde.
*   Late Afternoon (4:00 PM): Place de la Concorde &amp; Champs-Élysées.
    Admire the obelisk and walk along the famous avenue towards the
    Arc de Triomphe.
*   Evening (6:30 PM): Take the Metro to Montmartre. Explore the
    charming streets, visit the Sacré-Cœur Basilica, and enjoy the
    artistic atmosphere of Place du Tertre.
    Dinner (8:00 PM): Have dinner at a traditional bistro in
    Montmartre. Consider soupe à l&#39;oignon gratinée* (French onion soup).
    Budget ~€20-€30.

Day 3: History, Notre Dame &amp; Parisian Elegance

*   Morning (9:30 AM): Île de la Cité. Visit the Conciergerie
    (former royal palace and prison) and Sainte-Chapelle
    (stunning stained glass windows).
    Budget: Conciergerie ~€11.50, Sainte-Chapelle ~€11.50.
*   Lunch (12:30 PM): Enjoy a picnic lunch by the Seine River,
    with bread, cheese, and fruit from a local market. Budget ~€10-€15.
*   Afternoon (2:00 PM): Notre Dame Cathedral. View the cathedral
    from the outside (still under renovation but an important landmark).
    Walk around Île Saint-Louis and try Berthillon ice cream.
*   Late Afternoon (4:00 PM): Le Marais District. Explore the trendy
    Marais district, known for its Jewish heritage, art galleries,
    and boutiques. Visit Place des Vosges, the oldest planned square
    in Paris.
*   Dinner (7:30 PM): Have dinner in Le Marais. This area offers
    diverse cuisine options. Consider falafel or a crêperie. Budget ~€15-€25.

Day 4: Versailles Grandeur

*   Full Day (9:00 AM - 5:00 PM): Palace of Versailles.
    Take a train (RER C) to Versailles (approx. 45 minutes).
    Explore the palace, gardens, and Marie Antoinette&#39;s estate
    (the Petit Trianon and the Queen&#39;s Hamlet).
    Pre-book tickets! Palace and Estate of Trianon ticket ~€21.50.
*   Lunch (1:00 PM): Have lunch at one of the cafes in the
    Versailles gardens, or pack your own picnic to save money.
    Budget ~€10-€20.
*   Dinner (8:00 PM): Upon return to Paris,
    have dinner near your accommodation.

Day 5: Museums and Departure

*   Morning (9:30 AM): Musée d&#39;Orsay. Located in a
    former train station,
    this museum houses a stunning collection of Impressionist
    and Post-Impressionist art. Budget ~€16.
    Lunch (12:30 PM): Enjoy a final French lunch at a brasserie
    near the Musée d&#39;Orsay. Try a croque monsieur or salade niçoise*.
    Budget ~€15-€25.
*   Afternoon (2:00 PM): Depending on your departure time,
    you can visit the Centre Pompidou for modern art (~€15),
    do some souvenir shopping, or revisit a favorite spot.
*   Departure: Allow ample time to travel to the airport
    or train station.

Budget Considerations (per person per day - estimates):

*   Accommodation: €70-€120 (hostel private room/budget hotel)
*   Food: €40-€60 (mix of affordable eateries, picnics,
    and some restaurant meals)
*   Attractions: €20-€40 (depending on museum choices
    and pre-booking)
*   Transportation: €10-€15 (Metro/bus travel - consider a
    travel pass if using public transport frequently)

Tips for a Medium Budget Trip:

*   Accommodation: Consider staying in a hostel private room,
    a budget hotel in a less central location, or an Airbnb.
*   Transportation: Utilize the Paris Metro for efficient travel.
    Consider purchasing a Navigo Découverte pass for unlimited
    travel within zones 1-5.
    Food: Take advantage of affordable lunch options like formules*
    (set menus), crêpes, and sandwiches. Shop at local markets
    for picnic supplies.
*   Museums: Take advantage of free museum days (usually the first
    Sunday of the month, but check in advance). Consider the Paris
    Museum Pass if you plan to visit many museums.
*   Free Activities: Walk along the Seine, explore parks and gardens,
    visit free churches, and enjoy the atmosphere of different
    neighborhoods.

Enjoy your trip to Paris!
</code></pre><p>Alright, it detailed the itinerary with quite precise timings.
It already includes some culinary suggestions, but the next agent will make some concrete suggestions:</p>
<pre tabindex="0"><code>🤖 &gt; restaurant-suggester

Okay, I can help you enhance your Paris itinerary with restaurant
suggestions for lunch and dinner each day, keeping your medium budget
and focus on French cuisine in mind. I will use the Google Search Tool
to find restaurants near your planned activities.

Day 1: Iconic Paris &amp; Latin Quarter Charm

*   Morning (9:00 AM): Eiffel Tower. Pre-book tickets online to avoid
    long queues. Ascend to the second floor for stunning panoramic
    views. Budget ~€18-€29 (depending on lift access).
*   Lunch (12:00 PM): Enjoy a classic French crepe from a street
    vendor near the Eiffel Tower. Budget ~€8-€12.
    Restaurant Suggestion: Crêperie Générale* (near Eiffel Tower) -
    Quick and affordable crepes with various sweet and savory options.
*   Afternoon (1:30 PM): Champ de Mars &amp; Musée du Quai Branly -
    Jacques Chirac. Relax in the park with views of the tower.
    Then visit the museum dedicated to non-European art and cultures.
    Museum Budget: ~€12.
*   Late Afternoon (4:30 PM): Walk or take the Metro to the Latin Quarter.
    Explore the historic Sorbonne University and browse the
    Shakespeare and Company bookstore.
    Dinner (7:30 PM): Enjoy a traditional French meal at a bistro in
    the Latin Quarter. Look for set menus (formules) for better value.
    Try a steak frites or moules marinières*. Budget ~€20-€30.
    Restaurant Suggestion: Le *Bouillon Chartier* (Latin Quarter/Grands
    Boulevards area) - A historic and very popular bouillon serving
    classic French dishes at very affordable prices. Expect queues.

Day 2: Art, Gardens &amp; Montmartre Magic

*   Morning (9:00 AM): Musée du Louvre. Focus on key masterpieces
    like the Mona Lisa, Venus de Milo, and Winged Victory of Samothrace.
    Pre-book tickets! Budget: ~€17.
    Lunch (12:30 PM): Grab a quick and tasty sandwich jambon-beurre
    (ham and butter sandwich) from a boulangerie* near the Louvre.
    Budget ~€5-€8.
    Restaurant Suggestion: *Boulangerie Julien* (various locations
    including near the Louvre) - Known for its traditional jambon-beurre*
    and other delicious sandwiches and pastries.
*   Afternoon (2:00 PM): Tuileries Garden. Stroll through the beautiful
    gardens connecting the Louvre to Place de la Concorde.
*   Late Afternoon (4:00 PM): Place de la Concorde &amp; Champs-Élysées.
    Admire the obelisk and walk along the famous avenue towards the
    Arc de Triomphe.
*   Evening (6:30 PM): Take the Metro to Montmartre.
    Explore the charming streets, visit the Sacré-Cœur Basilica,
    and enjoy the artistic atmosphere of Place du Tertre.
    Dinner (8:00 PM): Have dinner at a traditional bistro in Montmartre.
    Consider soupe à l&#39;oignon gratinée* (French onion soup).
    Budget ~€20-€30.
    Restaurant Suggestion: Le *Relais Gascon* (Montmartre) -
    Hearty Southwestern French cuisine, known for its generous portions
    and reasonable prices.

Day 3: History, Notre Dame &amp; Parisian Elegance

*   Morning (9:30 AM): Île de la Cité. Visit the Conciergerie
    (former royal palace and prison) and Sainte-Chapelle
    (stunning stained glass windows).
    Budget: Conciergerie ~€11.50, Sainte-Chapelle ~€11.50.
*   Lunch (12:30 PM): Enjoy a picnic lunch by the Seine River,
    with bread, cheese, and fruit from a local market.
    Budget ~€10-€15.
    Restaurant Suggestion (for supplies): Marché Maubert*
    (near Île de la Cité) - A lively local market where you can
    buy fresh bread, cheese, fruits, and other picnic essentials.
*   Afternoon (2:00 PM): Notre Dame Cathedral.
    View the cathedral from the outside (still under renovation
    but an important landmark). Walk around Île Saint-Louis and
    try Berthillon ice cream.
*   Late Afternoon (4:00 PM): Le Marais District. Explore the trendy
    Marais district, known for its Jewish heritage, art galleries,
    and boutiques. Visit Place des Vosges, the oldest planned square
    in Paris.
*   Dinner (7:30 PM): Have dinner in Le Marais.
    This area offers diverse cuisine options.
    Consider falafel or a crêperie. Budget ~€15-€25.
    Restaurant Suggestion: *L&#39;As du Fallafel* (Le Marais) -
    Famous for its delicious and affordable falafel sandwiches.
    Expect a queue, but it moves quickly.

Day 4: Versailles Grandeur

*   Full Day (9:00 AM - 5:00 PM): Palace of Versailles.
    Take a train (RER C) to Versailles (approx. 45 minutes).
    Explore the palace, gardens, and Marie Antoinette&#39;s estate
    (the Petit Trianon and the Queen&#39;s Hamlet).
    Pre-book tickets! Palace and Estate of Trianon ticket ~€21.50.
*   Lunch (1:00 PM): Have lunch at one of the cafes in the
    Versailles gardens or pack your own picnic to save money.
    Budget ~€10-€20.
    Restaurant Suggestion: *La Flotille* (Versailles Gardens) -
    A casual restaurant in the gardens offering salads, sandwiches,
    and hot dishes. A convenient option if you don&#39;t want to pack a picnic.
*   Dinner (8:00 PM): Upon return to Paris, have dinner near your accommodation.
    Restaurant Suggestion: This depends on the location of your accommodation.
    If you provide this, I can give a more specific recommendation.
    Otherwise, search for a local brasserie or bistro* in your neighborhood.

Day 5: Museums and Departure

*   Morning (9:30 AM): Musée d&#39;Orsay. Located in a former train station,
    this museum houses a stunning collection of Impressionist and
    Post-Impressionist art. Budget ~€16.
    Lunch (12:30 PM): Enjoy a final French lunch at a brasserie
    near the Musée d&#39;Orsay.
    Try a croque monsieur or salade niçoise*.
    Budget ~€15-€25.
    Restaurant Suggestion: *Brasserie Thoumieux* (near Musée d&#39;Orsay) -
    A classic Parisian brasserie with a stylish Art Deco interior,
    serving traditional dishes.
*   Afternoon (2:00 PM): Depending on your departure time,
    you can visit the Centre Pompidou for modern art (~€15),
    do some souvenir shopping, or revisit a favorite spot.
*   Departure: Allow ample time to travel to the airport or train station.
    Restaurant Suggestion (if near Centre Pompidou): Crêperie Beaubourg*
    (near Centre Pompidou) - A good option for a quick and affordable
    crepe before heading to the airport.
</code></pre><p>There&rsquo;s a restaurant being suggested twice, as it&rsquo;s in the same area, another one that&rsquo;s not really French cuisine,
or sometimes it&rsquo;s some small food-on-the-go suggestsions to keep the budget on target.
In some places, the LLM would have liked more precise guidance to help you make a choice.
But overall, the culinary expert gave some good suggestions!</p>
</details>

<h2 id="when-to-choose-sequentialagent">When to choose <code>SequentialAgent</code></h2>
<ul>
<li>Choose a <strong><code>SequentialAgent</code></strong> when you have a fixed, linear process where Step B logically depends on the output of Step A. It provides predictability and structure.</li>
<li>Choose an <strong><code>LlmAgent</code> with sub-agents</strong> (as in <a href="https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/">part 1</a>) when you need flexibility and want the orchestrator LLM to dynamically decide the best tool or next step based on the conversational context.</li>
</ul>
<p>By understanding both patterns, you can build incredibly robust and sophisticated AI systems tailored to the specific problem you&rsquo;re trying to solve.</p>
<p>Stay tuned for the next part of the series, where we&rsquo;ll explore how to run agents in parallel to handle multiple tasks at once!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Mastering agentic workflows with ADK for Java: Sub-agents</title><link>https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/</link><pubDate>Wed, 23 Jul 2025 18:42:56 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/23/mastering-agentic-workflows-with-adk-sub-agents/</guid><description>&lt;p>Let me come back to the &lt;a href="https://github.com/google/adk-java">Agent Development Kit&lt;/a> (ADK) for Java!
We recently discussed the many &lt;a href="https://glaforge.dev/posts/2025/06/15/expanding-ai-agent-capabilities-with-tools/">ways to expand ADK agents with tools&lt;/a>.
But today, I want to explore the multi-agentic capabilities of ADK, by talking about &lt;strong>sub-agent workflows&lt;/strong>.&lt;/p>
&lt;p>In upcoming articles in this series, we&amp;rsquo;ll also talk about sequential, parallel, and loop flows.&lt;/p>
&lt;h2 id="the-divide-and-conquer-strategy">The &amp;ldquo;divide and conquer&amp;rdquo; strategy&lt;/h2>
&lt;p>Think of building a complex application. You wouldn&amp;rsquo;t put all your logic in a single, monolithic class, would you? You&amp;rsquo;d break it down into smaller, specialized components. The sub-agent workflow applies this same &amp;ldquo;divide and conquer&amp;rdquo; principle to AI agents.&lt;/p></description><content:encoded>
<![CDATA[<p>Let me come back to the <a href="https://github.com/google/adk-java">Agent Development Kit</a> (ADK) for Java!
We recently discussed the many <a href="https://glaforge.dev/posts/2025/06/15/expanding-ai-agent-capabilities-with-tools/">ways to expand ADK agents with tools</a>.
But today, I want to explore the multi-agentic capabilities of ADK, by talking about <strong>sub-agent workflows</strong>.</p>
<p>In upcoming articles in this series, we&rsquo;ll also talk about sequential, parallel, and loop flows.</p>
<h2 id="the-divide-and-conquer-strategy">The &ldquo;divide and conquer&rdquo; strategy</h2>
<p>Think of building a complex application. You wouldn&rsquo;t put all your logic in a single, monolithic class, would you? You&rsquo;d break it down into smaller, specialized components. The sub-agent workflow applies this same &ldquo;divide and conquer&rdquo; principle to AI agents.</p>
<p>Instead of one &ldquo;do-it-all&rdquo; agent, you create a hierarchy:</p>
<ul>
<li>An <strong>orchestrator agent</strong> that acts as a project manager.</li>
<li>Several <strong>specialized sub-agents</strong> that act as team members, each with a specific skill.</li>
</ul>
<p>This approach has some important advantages:</p>
<ul>
<li><strong>Clarity and focus:</strong> Each agent gets a clear, concise set of instructions (its system prompt). A focused agent is a more reliable and predictable agent. It&rsquo;s less prone to hallucinations, or being lost at the complexity of the task at hand.</li>
<li><strong>Modularity and reusability:</strong> An agent designed for a specific task, like summarizing text, can be reused across many different applications. So you can play Lego bricks to build your next agents!</li>
<li><strong>Maintainability:</strong> Debugging or enhancing a small, focused agent is infinitely easier than untangling the logic of a massive, overburdened one, with potentially conflicting system instructions to tackle all the corner cases of each situation.</li>
</ul>
<h2 id="a-practical-example-a-_content-companion_">A practical example: a <em>content companion</em></h2>
<p>Let&rsquo;s make this concrete by looking at an example I&rsquo;ve been playing with:
a &ldquo;content companion,&rdquo; an AI assistant designed to help bloggers and influencers research topics and draft social media posts.</p>
<p>This system is a perfect illustration of a sub-agent hierarchy, composed of a few distinct agents working in concert.</p>

<details>
  <summary>Click to see the full source code, before diving in</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>searchAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;google-search-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        An agent that searches on Google Search
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to search on Google Search.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the Google Search Tool to search up-to-date
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and relevant information about the topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>topicSearchAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;topic-search-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        An agent that searches and dives in particular topics
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to help explore a particular topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `google-search-agent` tool to search up-to-date
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and relevant information about the topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Be sure to display the result of the search
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to inform the user.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(AgentTool.<span style="color:#4070a0">create</span>(searchAgent))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">afterAgentCallback</span>(callbackContext<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>callbackContext.<span style="color:#4070a0">eventActions</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setTransferToAgent</span>(<span style="color:#4070a0">&#34;content-companion&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">empty</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>socialMediaAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;social-media-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        An agent that crafts social media posts about a topic
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Given content about a topic, your role is to craft
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        an attractive social media post about it.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t hesitate to use meaningful emojis
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        when it helps convey the message.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">afterAgentCallback</span>(callbackContext<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>callbackContext.<span style="color:#4070a0">eventActions</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setTransferToAgent</span>(<span style="color:#4070a0">&#34;content-companion&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">empty</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;content-companion&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        A content companion that searches topics
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and crafts compelling social media stories
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to help bloggers and influencers
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        come up with interesting topic ideas,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to search information about the topic to write about,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and potentially to craft a compelling social media post.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t search yourself:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `topic-search-agent`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to find information about a topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t write social media posts yourself:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `social-media-agent`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to craft a social media post about the topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(socialMediaAgent,<span style="color:#bbb"> </span>topicSearchAgent)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<p>Let&rsquo;s zoom in on the various components.</p>
<h3 id="1-the-orchestrator-content-companion">1. The orchestrator: <code>content-companion</code></h3>
<p>At the top of our hierarchy is the <code>content-companion</code>. This is the agent we, the user, will interact with. Its main job is to understand our high-level goal and delegate the actual work to the right specialist.</p>
<p>Notice its instructions: it&rsquo;s explicitly told <em>what not to do</em>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;content-companion&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        A content companion that searches topics
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and crafts compelling social media stories
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to help bloggers and influencers
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        come up with interesting topic ideas,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to search information about the topic to write about,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and potentially to craft a compelling social media post.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t search yourself: Use the `topic-search-agent`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to find information about a topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t write social media posts yourself:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `social-media-agent` to craft
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        a social media post about the topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">subAgents</span>(socialMediaAgent,<span style="color:#bbb"> </span>topicSearchAgent)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Sub-agents!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>.subAgents()</code> method is where the magic happens. It registers the <code>socialMediaAgent</code> and <code>topicSearchAgent</code> (detailed further down below)
and makes them available as callable agents for the <code>content-companion</code>. The orchestrator doesn&rsquo;t need to know <em>how</em> they work, just <em>what</em> they do.</p>
<p>Now lets turn our attention to the sub-agents.</p>
<h3 id="2-the-specialists-the-sub-agents">2. The specialists: The sub-agents</h3>
<p>The <code>content-companion</code> manages two direct reports:</p>
<ul>
<li><strong><code>topic-search-agent</code></strong>: the research assistant.</li>
<li><strong><code>social-media-agent</code></strong>: the creative copywriter.</li>
</ul>
<p>Each of these is an independent <code>LlmAgent</code> with its own focused prompt. For example, the <code>social-media-agent</code> is single-mindedly focused on its creative task:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>socialMediaAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;social-media-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        An agent that crafts social media posts about a topic
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Given content about a topic, your role is to craft
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        an attractive social media post about it.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t hesitate to use meaningful emojis
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        when it helps convey the message.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="3-deeper-delegation-agents-as-tools">3. Deeper delegation: Agents as tools</h3>
<p>Here&rsquo;s where it gets interesting.
The delegation doesn&rsquo;t have to stop at one level.
As I covered in my previous post on <a href="https://glaforge.dev/posts/2025/06/15/expanding-ai-agent-capabilities-with-tools/">expanding agent capabilities with tools</a>,
one agent can be used as a tool by another.</p>
<p>Our <code>topic-search-agent</code> needs to search the web, but we want to abstract that capability. So, it delegates the raw search functionality to an even more specialized agent.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// The lowest-level worker,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// whose only job is to use the GoogleSearchTool</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>searchAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;google-search-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// The mid-level manager,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// which uses the searcher as a tool</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>topicSearchAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;topic-search-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to help explore a particular topic.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Use the `google-search-agent` tool...
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(AgentTool.<span style="color:#4070a0">create</span>(searchAgent))<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// An agent becomes a tool!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>By wrapping <code>searchAgent</code> with <code>AgentTool.create()</code>, we turn a fully-fledged agent into a simple, callable tool. This is a powerful abstraction that keeps our agent responsibilities cleanly separated.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>In our use case, this is also necause of a technical limitation:
in ADK, with Gemini, you can&rsquo;t have multiple tools configured when a built-in tool like the <code>GoogleSearchTool</code> is declared.
So using <em>agents-as-tools</em> also helps circumventing this limitation.</p>
      </div>
    </div><h2 id="tracing-the-flow-of-delegation">Tracing the flow of delegation</h2>
<p>Let&rsquo;s trace a simple request to see how this all fits together:</p>
<ol>
<li><strong>Me:</strong> <em>&ldquo;Find me some information about the latest planet discoveries in 2025.&rdquo;</em></li>
<li><strong><code>content-companion</code>:</strong> It receives the request. Its instructions forbid it from searching. It sees that the <code>topic-search-agent</code> is the right tool for the job and invokes it.</li>
<li><strong><code>topic-search-agent</code>:</strong> It&rsquo;s now active. Its instructions tell it to use the <code>google-search-agent</code> tool. It calls this tool to perform the search.</li>
<li><strong><code>google-search-agent</code>:</strong> This agent&rsquo;s only job is to execute its tool, <code>GoogleSearchTool</code>. It runs the search and returns the raw results.</li>
<li><strong>The results flow back up the chain:</strong> The raw data goes from <code>google-search-agent</code> to <code>topic-search-agent</code>. The <code>topic-search-agent</code> might then process or summarize these results before passing them up to the <code>content-companion</code>, which finally presents the answer to me.</li>
</ol>
<p>Then via a new interaction, you can ask for a tweet about the discoveries:</p>
<ol>
<li><strong>Me:</strong> <em>&ldquo;Craft a short tweet about those discoveries.&rdquo;</em></li>
<li><strong><code>social-media-agent</code>:</strong> It receives the request, and remembers the ongoing conversation about exoplanets, and will prepare a tweet as requested.</li>
<li><strong>The result is shared with the <code>content-companion</code></strong>, which will deliver the final tweet suggestion back to me.</li>
</ol>
<h2 id="choreographing-the-cadk-onversation-with-afteragentcallback">Choreographing the cadk onversation with <code>afterAgentCallback</code></h2>
<p>If you look closely at the entire Java code posted at the beginning of the article,
you&rsquo;ll spot a curious addition to our specialist agents: an <code>afterAgentCallback</code>.
This isn&rsquo;t just boilerplate; it&rsquo;s a crucial piece of conversational choreography.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>.<span style="color:#4070a0">afterAgentCallback</span>(callbackContext<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>callbackContext.<span style="color:#4070a0">eventActions</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setTransferToAgent</span>(<span style="color:#4070a0">&#34;content-companion&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Maybe.<span style="color:#4070a0">empty</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>})<span style="color:#bbb">
</span></span></span></code></pre></div><p>So, what does this do? It explicitly manages the flow of control.</p>
<p>Without this callback, after the <code>topic-search-agent</code> finishes its research, the conversational focus would remain with it.
If I then said, <em>&ldquo;Okay, now write a tweet about that,&rdquo;</em> the <code>topic-search-agent</code> would be confused. That&rsquo;s not its job.
I&rsquo;d be <em>&ldquo;stuck&rdquo;</em> talking to the specialist, and I&rsquo;d have to manually re-engage the main <code>content-companion</code>.
That&rsquo;s a clunky user experience.</p>
<p>The <code>afterAgentCallback</code> solves this elegantly.
It acts as a <em>&ldquo;return to sender&rdquo;</em> instruction. The <code>setTransferToAgent(&quot;content-companion&quot;)</code> command tells the ADK:
<em>&ldquo;As soon as this agent&rsquo;s turn is over, immediately transfer the conversational control back to the <code>content-companion</code>.&rdquo;</em></p>
<p>This ensures the user always has a smooth, continuous dialogue with the main orchestrator, which is always ready for the next command. It&rsquo;s a vital mechanism for designing complex yet intuitive agentic systems.</p>
<h2 id="why-bother-with-agent-hierarchies">Why bother with agent hierarchies?</h2>
<p>Creating a sub-agent workflow makes the difference when:</p>
<ul>
<li>Your problem is multi-faceted, like <em>&ldquo;research a topic, write a summary, and then draft a tweet.&rdquo;</em></li>
<li>You want to build a system that is robust and easily extensible. Need a new capability? Just build a new specialist agent and register it with your orchestrator.</li>
<li>You want your prompts to be simple and effective. Less ambiguity in the prompt leads to more reliable behavior from the LLM.</li>
</ul>
<p>By composing agents this way, we move from simple <em>command-and-response</em> bots to building truly scalable and capable AI systems.</p>
<p>Stay tuned for our next post! We&rsquo;ll explore <strong>sequential workflows</strong> to orchestrate tasks that must happen in a specific, predictable order.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The Sci-Fi naming problem: Are LLMs less creative than we think?</title><link>https://glaforge.dev/posts/2025/07/22/the-sci-fi-naming-problem-are-llms-less-creative-than-we-think/</link><pubDate>Tue, 22 Jul 2025 17:13:10 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/22/the-sci-fi-naming-problem-are-llms-less-creative-than-we-think/</guid><description>&lt;p>Like many developers, I&amp;rsquo;ve been exploring the creative potential of Large Language Models (LLMs). At the beginning of the year, I crafted a project to build an &lt;a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">AI agent that could generate short science-fiction stories&lt;/a>. I used &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a> to create a deterministic workflow to drive &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-pro?utm_campaign=CDR_0x7a40493f_default_b433495891&amp;amp;utm_medium=external&amp;amp;utm_source=blog">Gemini&lt;/a> for the story generation, and &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/imagen/4-0-generate-preview-06-06?utm_campaign=CDR_0x7a40493f_default_b433495891&amp;amp;utm_medium=external&amp;amp;utm_source=blog">Imagen&lt;/a> for the illustrations. The initial results were fascinating. The model could weave narratives, describe futuristic worlds, and create characters with seemingly little effort. But as I generated more stories, a strange and familiar pattern began to emerge…&lt;/p></description><content:encoded>
<![CDATA[<p>Like many developers, I&rsquo;ve been exploring the creative potential of Large Language Models (LLMs). At the beginning of the year, I crafted a project to build an <a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">AI agent that could generate short science-fiction stories</a>. I used <a href="https://docs.langchain4j.dev/">LangChain4j</a> to create a deterministic workflow to drive <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-pro?utm_campaign=CDR_0x7a40493f_default_b433495891&amp;utm_medium=external&amp;utm_source=blog">Gemini</a> for the story generation, and <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/imagen/4-0-generate-preview-06-06?utm_campaign=CDR_0x7a40493f_default_b433495891&amp;utm_medium=external&amp;utm_source=blog">Imagen</a> for the illustrations. The initial results were fascinating. The model could weave narratives, describe futuristic worlds, and create characters with seemingly little effort. But as I generated more stories, a strange and familiar pattern began to emerge…</p>
<p>A &ldquo;Dr. Thorne&rdquo; would frequently appear, sometimes as a brilliant scientist, other times as a starship captain. The heroines were often named Anya, or Elena. The stories unfolded on planets with names that all seemed to echo each other (but often inspired by current exoplanet findings). It was as if the AI was drawing from a very small, very specific cast of characters and settings.</p>
<p>My first thought was that this was a limitation of the specific model I was using, Google&rsquo;s Gemini. Was it simply not &ldquo;creative&rdquo; enough? Was its imagination stuck in a loop? I was about to chalk it up to a model-specific quirk, but then I stumbled upon a benchmark for long-form creative writing: <a href="https://eqbench.com/creative_writing_longform.html">EQ Bench’s longform creative writing</a>. To my surprise, I noticed that other models from different providers were also generating sci-fi stories with eerily similar character names. <strong>The problem wasn&rsquo;t isolated; it was systemic</strong>.</p>
<ul>
<li>The Gemini 2.5 Pro sci-fi <a href="https://eqbench.com/results/creative-writing-longform/gemini-2.5-pro-preview-03-25_longform_report.html">sample</a> on the benchmark had its main character called… Dr Aris Thorne.</li>
<li>DeepSeek’s <a href="https://eqbench.com/results/creative-writing-longform/deepseek-ai__DeepSeek-V3-0324_longform_report.html">novel</a> was featuring Elara Voss as its main protagonist.</li>
<li>Claude Opus 4’s <a href="https://eqbench.com/results/creative-writing-longform/claude-opus-4_longform_report.html">sample</a> was talking about Elana Vasquez. Common names I could find across models multiple times.</li>
</ul>
<p>This discovery shifted my perspective. What if the issue wasn&rsquo;t a lack of creativity, but a reflection of the data the models were trained on? The hypothesis was simple: <strong>for a specialized genre like science fiction, perhaps the available training data is more limited than we assume</strong>. If the models were all learning from a similar, relatively small pool of sci-fi literature, it would stand to reason they would reproduce the most common elements from that pool.</p>
<h1 id="hunting-for-science-fiction-datasets"><strong>Hunting for science-fiction datasets</strong></h1>
<p>This led me down a rabbit hole to <a href="https://www.kaggle.com/">Kaggle</a>, a popular platform for data scientists, where I searched for science-fiction book datasets. My intuition was that <strong>big models were trained on common datasets of novels</strong>. And for a very precise topic, the actual data contained in those dataset was probably scarce, leading to less diversity. I found exactly what I was looking for: large text corpuses of sci-fi novels. My intuition nagging at me, I began searching through these datasets for the very names that had been popping up in my generated stories.</p>
<ul>
<li>The <a href="https://www.kaggle.com/datasets/tanguypledel/science-fiction-books-subgenres">Science Fiction Books dataset</a> featured 10 thousands books, but only with metadata, but the book descriptions cover the main characters, the narrative, etc. The data is split across several CSV files for different types (or sub-genres) of sci-fi stories.</li>
<li>The <a href="https://www.kaggle.com/datasets/jannesklaas/scifi-stories-text-corpus">SciFi Stories Text corpus</a> is a ~150MB text file containing various sci-fi stories (however I’m not sure exactly what it covers and where the data was coming from).</li>
</ul>
<p>I can’t say if the big models were trained on those particular datasets, but there they were. Dr. Thorne. Elara. Anya…</p>
<p>The names weren&rsquo;t just present; they were frequent. The models weren&rsquo;t failing at being creative. They were succeeding, perhaps too well, at <strong>identifying and reproducing the most statistically common patterns in the data</strong> they were fed. The perceived lack of creativity was, in fact, a direct consequence of the limitations of the training data for this specific genre.</p>
<h1 id="gemini-cli-to-the-rescue"><strong>Gemini CLI to the rescue</strong></h1>
<p>I pointed <a href="https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/?utm_campaign=CDR_0x7a40493f_default_b433495891&amp;utm_medium=external&amp;utm_source=blog">Gemini CLI</a> at the CSV files and at this big text file of sci-fi novels from the 2 datasets I mentioned, and asked it to find some references to characters&hellip;</p>
<p><figure>
  <a href="#img-3fc3b6878c66d380891dfac89d67ac98">
    <img src="/img/gemini-cli/ai-story-gemini-cli-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3fc3b6878c66d380891dfac89d67ac98">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/ai-story-gemini-cli-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The infamous Dr Thorne appeared 204 times in 26 book descriptions, and Anya was present in 8 book descriptions and made some 204 appearances! So they were clearly very well known!</p>
<p>After various searches through the datasets, Gemini CLI told me:</p>
<p><figure>
  <a href="#img-7e0b0684ba4f4be81180123f0b0eb942">
    <img src="/img/gemini-cli/ai-story-gemini-cli-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-7e0b0684ba4f4be81180123f0b0eb942">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini-cli/ai-story-gemini-cli-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Thorne, Anya, Althea, Elena were very busy characters in the science-fiction novels! That’s certainly why my <a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">short sci-fi story generator</a> was often yielding the same characters&rsquo; names.</p>
<h1 id="conclusion"><strong>Conclusion</strong></h1>
<p>This experience reveals a crucial nuance in how we should think about AI and creativity. We often treat LLMs as black boxes of boundless imagination. But in reality, <strong>their creative output is a mirror reflecting the data they have ingested</strong>. For broad topics, where the training data is vast and diverse (the entire internet, for example), this mirror is so large and multifaceted that the reflections appear endlessly unique. But for more niche domains, like the specific subgenres of science fiction, the mirror is smaller. The reflections become more focused, more repetitive, and the patterns become obvious.</p>
<p>So, are LLMs <em>uncreative</em>? The answer is more complex than a simple yes or no. Their creativity is not one of imagination in the human sense, but of sophisticated pattern recognition and recombination. When the patterns are limited, so is the apparent creativity. This doesn&rsquo;t diminish their power as tools, but it does highlight the <strong>critical role of data diversity</strong>. For AI to be a truly powerful creative partner in specialized fields, it needs to be fed a rich and varied diet of information from that domain.</p>
<p>But it gave me some ideas for improving my story generator, to further enhance its creativity, by focusing on creating more diverse names first, regardless of the science-fiction focus (to avoid staying in the pit of the common names), building up their profiles, and only then injecting them in the context of the sci-fi world… So I may come back to this creative process in upcoming episodes on this blog!</p>
<p>Until then, we may have to get used to hearing more tales of Dr. Thorne and his adventures across the galaxy! To infinity and beyond!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>AI Agents, the New Frontier for LLMs</title><link>https://glaforge.dev/talks/2025/07/16/ai-agents-the-new-frontier-for-llms/</link><pubDate>Wed, 16 Jul 2025 12:14:22 +0200</pubDate><guid>https://glaforge.dev/talks/2025/07/16/ai-agents-the-new-frontier-for-llms/</guid><description>&lt;p>I recently gave a talk titled &lt;strong>&amp;ldquo;AI Agents, the New Frontier for LLMs&amp;rdquo;&lt;/strong>. The session explored how we can move beyond simple request-response interactions with Large Language Models to build more sophisticated and autonomous systems.&lt;/p>
&lt;p>If you&amp;rsquo;re already familiar with LLMs and Retrieval Augmented Generation (RAG), the next logical step is to understand and build AI agents.&lt;/p>
&lt;h2 id="what-makes-a-system-agentic">What makes a system &amp;ldquo;agentic&amp;rdquo;?&lt;/h2>
&lt;p>An agent is more than just a clever prompt. It’s a system that uses an LLM as its core reasoning engine to operate autonomously. The key characteristics that make a system &amp;ldquo;agentic&amp;rdquo; include:&lt;/p></description><content:encoded>
<![CDATA[<p>I recently gave a talk titled <strong>&ldquo;AI Agents, the New Frontier for LLMs&rdquo;</strong>. The session explored how we can move beyond simple request-response interactions with Large Language Models to build more sophisticated and autonomous systems.</p>
<p>If you&rsquo;re already familiar with LLMs and Retrieval Augmented Generation (RAG), the next logical step is to understand and build AI agents.</p>
<h2 id="what-makes-a-system-agentic">What makes a system &ldquo;agentic&rdquo;?</h2>
<p>An agent is more than just a clever prompt. It’s a system that uses an LLM as its core reasoning engine to operate autonomously. The key characteristics that make a system &ldquo;agentic&rdquo; include:</p>
<ul>
<li><strong>Planning and decomposition</strong>: The ability to break down a complex goal into a sequence of smaller, manageable steps.</li>
<li><strong>Tool use</strong>: The capacity to interact with external systems, APIs, or data sources to gather information or perform actions (via <em>&ldquo;function calling&rdquo;</em>). This could be anything from searching the web to querying a database or calling a specific function.</li>
<li><strong>Reflection</strong>: The capability to analyze its own actions and their outcomes, learn from mistakes, and refine its plan to achieve the final objective.</li>
</ul>
<p>This matters because it&rsquo;s a fundamental shift from simply <em>asking</em> an LLM for information to <em>tasking</em> a system with achieving a goal. An agent can handle ambiguity and orchestrate a series of operations to deliver a result that a single LLM call cannot.</p>
<h2 id="common-design-patterns"><strong>Common Design Patterns</strong></h2>
<p>To build these agents, we rely on established design patterns that provide structure to their autonomous behavior. In the talk, I cover several of these with concrete code examples, including:</p>
<ul>
<li><strong>ReAct (Reason and Act)</strong>: This is a foundational pattern where the agent iterates through a loop of reasoning about the next best action, taking that action (often with a tool), and then observing the outcome to inform its next step.</li>
<li><strong>Function calling</strong>: This allows the model to declare that it needs to invoke an external tool or function and to provide the necessary arguments. The system then executes the function and feeds the result back to the model so it can proceed.</li>
<li><strong>Human-in-the-Loop</strong>: For tasks that require validation, approval, or handling ambiguity, this pattern ensures that the agent can pause its execution and request input from a human user before continuing.</li>
</ul>
<hr />
<p>The presentation demonstrates these concepts with practical examples, including a RAG agent and a story-generation application, using frameworks like <a href="https://docs.langchain4j.dev/">LangChain4j</a> and the new <a href="https://github.com/google/adk-java">Agent Development Kit</a> (ADK, in particular ADK for Java). I also touch on emerging standards for agent-to-agent communication (via the <a href="https://a2aproject.github.io/A2A/latest/">A2A</a> protocol), and how to interact with external tools via <a href="http://modelcontextprotocol.io/">MCP</a> (the Model Context Protocol).</p>
<p>If you are interested in learning how to build systems that can reason, plan, and act, you can find the full recording (for now only in French) and the accompanying slides below.</p>
<h2 id="the-abstract">The abstract</h2>
<blockquote>
<p>Know Large Language Models at your fingertips? Mastering Retrieval Augmented Generation to help an LLM search your documents? It&rsquo;s time to dive into the wonderful world of intelligent agents, the next frontier for LLMs!</p>
<p>In this session, we will first define what agents are, or at least what makes a system &ldquo;agentic&rdquo;. We will explain the limitations of LLMs. Then, through concrete examples, we will implement different agents in Java, using the LangChain4j framework and ADK (the Agent Development Kit), to illustrate some typical agent patterns and to understand how to go beyond a simple LLM call to obtain responses that meet the needs of your users, or even to trigger actions with the surrounding system.</p>
<p>But it’s not all we’ll learn about! An agent doesn’t live alone on a desert tropical island. Indeed it can communicate with other agents via tools that can be invoked thanks to the Model Context Protocol (MCP). They can also interact with other remote agents from other platforms and ecosystems, thanks to the Agent To Agent protocol (A2A).</p>
<p>Are you ready for the next hype on agents? Come and discover it in this session!</p></blockquote>
<h2 id="the-slide-deck">The slide deck</h2>
<script async class="speakerdeck-embed" data-id="ff7e2bd87c264008bc8ca8cb8112f936" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="the-video-of-the-talk">The video of the talk</h2>
<p>I had the chance to give this talk at <a href="https://www.devoxx.fr/agenda-2025/speaker/guillaume-laforge/">Devoxx France</a>.
The only recording I have right now in French &#x1f1eb;&#x1f1f7;, but hopefully, once this talk is available in English, I&rsquo;ll update this post to also share an English version of it.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Yv7NX4cDxuI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Advanced RAG — Using Gemini and long context for indexing rich documents (PDF, HTML...)</title><link>https://glaforge.dev/posts/2025/07/14/advanced-rag-using-gemini-and-long-context-for-indexing-rich-documents/</link><pubDate>Sun, 13 Jul 2025 11:25:25 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/14/advanced-rag-using-gemini-and-long-context-for-indexing-rich-documents/</guid><description>&lt;p>A very common question I get when presenting and talking about advanced RAG
(Retrieval Augmented Generation) techniques, is
&lt;strong>how to best index and search rich documents like PDF&lt;/strong> (or web pages),
that contain both text and rich elements, like pictures or diagrams.&lt;/p>
&lt;p>Another very frequent question that people ask me is about &lt;strong>RAG versus long context windows&lt;/strong>.
Indeed, models with long context windows usually have a more global understanding of a document,
and each excerpt in its overall context. But of course,
you can&amp;rsquo;t feed all the documents of your users or customers in one single augmented prompt.
Also, RAG has other advantages like offering a much lower latency, and is generally cheaper.&lt;/p></description><content:encoded>
<![CDATA[<p>A very common question I get when presenting and talking about advanced RAG
(Retrieval Augmented Generation) techniques, is
<strong>how to best index and search rich documents like PDF</strong> (or web pages),
that contain both text and rich elements, like pictures or diagrams.</p>
<p>Another very frequent question that people ask me is about <strong>RAG versus long context windows</strong>.
Indeed, models with long context windows usually have a more global understanding of a document,
and each excerpt in its overall context. But of course,
you can&rsquo;t feed all the documents of your users or customers in one single augmented prompt.
Also, RAG has other advantages like offering a much lower latency, and is generally cheaper.</p>
<p>However, the answer I usually give is that you can take the best of both worlds, with a <strong>hybrid approach</strong>:</p>
<ul>
<li>You can <strong>use a RAG approach</strong> (or a mix of keyword search engine, graph RAG, or vector-based RAG) to find relevant documents for the user query.</li>
<li>And then <strong>feed only those key documents in the context window</strong> of a model like <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstart?usertype=adc&amp;utm_campaign=CDR_0x7a40493f_default_b431756993&amp;utm_medium=external&amp;utm_source=blog">Gemini</a> that accepts 1+ million tokens.</li>
</ul>
<p>That way, the model can focus on whole documents, with a finer understanding of each one,
but is not overwhelmed with too many documents to find the needle in the haystack.</p>
<blockquote>
<p>The current trending topic of <strong>context engineering</strong> is exactly about this:
it&rsquo;s crucial to give the best contextual information to your favorite LLM!</p></blockquote>
<h1 id="using-gemini-to-finely-index-a-rich-pdf-document-for-rag-search"><strong>Using Gemini to finely index a rich PDF document for RAG search</strong></h1>
<p>Before feeding the relevant document in the context window of the LLM, we first need to index it.</p>
<h2 id="about-hypothetical-question-generation">About hypothetical question generation</h2>
<p>In my last article, I explored an interesting technique called <a href="https://glaforge.dev/posts/2025/07/06/advanced-rag-hypothetical-question-embedding/">Hypothetical Questions embedding</a>. This technique works great with question/answer tasks, as the idea is to compare <em>&ldquo;hypothetical&rdquo;</em> questions to user questions. This tends to give higher levels of similarity when calculating the vector similarity for the semantic search.</p>
<p>One aspect I haven&rsquo;t explained in detail is how many questions we should generate for a given paragraph or set of sentences. It seemed to me that 10 questions or so per chunks of 2000 characters or less was a good rule of thumb. But if you use an LLM to parse a document, and let it figure out sections of text that make sense together, the LLM can as well figure out which questions to ask, and how many of them, depending on both the length of the given text, and its semantic density.</p>
<h2 id="what-about-diagrams-and-other-pictures">What about diagrams and other pictures?</h2>
<p>Sometimes customers I talk to manage to extract and embed the text in rich documents, however, they&rsquo;re not sure how to treat the various diagrams and images in those documents. They may also use an image model to calculate embeddings for whole pages (containing both the text and the pictures). It&rsquo;s possible to use image models to extract bounding boxes around images and then somehow extract just the boxes containing the pictures. Then they analyze the picture, get a text description, and embed that text.</p>
<p>Again, a multimodal model like Gemini is actually able to look at each page, see where all the diagrams are, and see all the images in each page, to extract some meaningful description.</p>
<h2 id="-the-idea-use-an-llm-to-chunk-text-generate-questions-and-describe-pictures">💡 The idea: Use an LLM to chunk text, generate questions, and describe pictures</h2>
<p>All at once! Let’s use Gemini to do this for us (or any multimodal LLM with a context window large enough to contain a whole document of your corpus). We’ll use structured output to be get a JSON output that you can easily parse to extract the key information (chunks, questions, image descriptions, but also pages or titles).</p>
<h1 id="the-smart-prompt"><strong>The smart prompt</strong></h1>
<p>Let’s have a look at the following prompt which we can use as system instructions for an LLM call:</p>
<pre tabindex="0"><code>Your goal is to analyze long documents for Retrieval Augmented
Generation (RAG) by splitting the content in pieces.
The content pieces will be the chunks that will be embedded
thanks to an embedding model to calculate vector embedding
to store in a vector database.

Given a long PDF document, you must extract and format the
content so that each piece of content is meaningful
as a standalone unit of text.

Each piece of content should be tagged with the page number
it appears in, and the title of the section it&#39;s situated in,
as well as a list of questions whose answers can be provided
by this piece of content.

If there is a table of content, you can use it for adding
more context to a given excerpt, or for the section titles,
but don&#39;t return the table of content as an excerpt.

When you encounter an image, a picture, or a diagram,
the piece of content you return should be the description
of the image with as much detail as possible.
If the figure has a caption, use it as the title
of the piece of content.
Like for text excerpts, use the title of the current section
the image is in in its description.

Don&#39;t create pieces of text for menu, navigation,
and other unrelated elements.
But be sure to create a piece of content for all the text
of the document.
Go through ALL the pages of the document.
The piece of text should be the exact text found in the article.
DON&#39;T change a single world. DON&#39;T summarize.
</code></pre><p>Let’s examine the prompt, but feel free to enhance or expand it for your needs:</p>
<ul>
<li>The first paragraph gives the general <strong>goal</strong> we try to achieve, and explains the general idea of the task we ask the LLM.</li>
<li>The second paragraph tells the LLM it’s important to <strong>create pieces of content</strong> (chunks!) <strong>that are meaningful</strong>. It tends to help it find relevant chunks that talk about the same topic or aspect, instead of splitting the document into random and/or fixed length spaces.</li>
<li>The third paragraph details what information to carry for each individual piece of content. We want to retain:
<ul>
<li>the <strong>page number</strong> (in particular for PDF documents),</li>
<li>the <strong>title of the section</strong> the text appears in (if it’s a structured document with various sections and header titles),</li>
<li>a <strong>list of questions</strong> in the spirit of the Hypothetical Question Embedding technique we’ve used in the previous article,</li>
<li>and of course the <strong>chunk</strong> itself.</li>
</ul>
</li>
<li>The fourth paragraph says that we don’t need to create chunks for the table of contents, but that a table of contents can actually be useful for crafting the titles of the sections.</li>
<li>The fifth paragraph now talks about diagrams, pictures, etc. It tells the LLM it should actually create a detailed description of the image as a textual representation. It also advises to pay attention to captions or the section the picture appears in.</li>
<li>The last paragraph suggests to ignore certain elements of the page or document, like navigation elements, and also ask the LLM to create chunks of text for the whole document without missing anything, and to quote the text as is, without any rewriting or summarization.</li>
</ul>
<p>Generally, the LLM will generate a number of questions that makes sense for the length and semantic density of the chunk. A smaller paragraph with more pieces of key information could yield more questions than a much longer paragraph that doesn’t carry much semantic meaning.</p>
<p>It’s important to give further advice on which content it can safely ignore, or that it should really take the text verbatim without any creative changes it could think of.</p>
<p>For the illustrations, not only will the LLM give you a detailed description of them, but it will also generate questions that can be answered by these illustrations. For example, if it’s a picture of a flag of Germany, it may generate a question like what are the colors of the German flag.</p>
<h2 id="defining-the-structured-output">Defining the structured output</h2>
<p>In order to guide the LLM to generate a useful JSON output that you can then later parse easily, you can define its structured output JSON schema. Models like Gemini support this feature. Sometimes, some models require you to define the format of the JSON output via prompting, but more modern and bigger models usually support this feature out of the box.</p>
<p>The structured output I defined:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;array&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;items&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;object&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;properties&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;string&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;The title of the content.&#34;</span>
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;string&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;The text content.&#34;</span>
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;page&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;integer&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;The page number.&#34;</span>
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;questions&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;array&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;A list of questions whose answers can be found in this pience of text.&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;items&#34;</span>: {
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;string&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;required&#34;</span>: [<span style="color:#4070a0">&#34;title&#34;</span>, <span style="color:#4070a0">&#34;text&#34;</span>, <span style="color:#4070a0">&#34;page&#34;</span>, <span style="color:#4070a0">&#34;questions&#34;</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here, we have an array of pieces that contain a title, a text chunk (which might be the detailed description of a picture), a page, and a list of hypothetical questions. All those fields are required.</p>
<p>I didn’t put any top level information like the title of the whole document, its location (URL/URI or unique ID), or potentially some tags that help categorize the document. But these are extremely useful bits of information you can also request the LLM to generate for you. I wrote about <a href="https://glaforge.dev/posts/2024/08/12/let-llm-suggest-instagram-hashtags/">using LLMs to tag pictures</a> in an older article.</p>
<h1 id="the-prompt-in-action"><strong>The prompt in action</strong></h1>
<p>I’ve exported the Wikipedia article about the city of <a href="https://en.wikipedia.org/wiki/Berlin">Berlin</a>. I saved it as a PDF document.
Then I used the prompt from the previous section as <strong>system instructions</strong>. I passed the PDF document as a user message. I simply tweaked my prompt by using <a href="https://aistudio.google.com/prompts/new_chat">Google AI Studio</a> but feel free to use any tool you prefer.</p>
<p>Let’s see some extracts of the JSON that Gemini generated for this PDF.</p>
<p>For the first chunk, it followed exactly the content of the first paragraph of the Wikipedia page:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;page&#34;</span>: <span style="color:#40a070">1</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;questions&#34;</span>: [
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;What is Berlin&#39;s status in Germany?&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;What is the population of Berlin?&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;Which state surrounds Berlin?&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;What is the population of Berlin&#39;s urban and metropolitan areas?&#34;</span>
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: <span style="color:#4070a0">&#34;Berlin (/bɜːrˈlɪn/ bur-LIN; German: [bɛɐ̯ˈliːn]) is the capital and largest city of Germany, by both area and population.[10] With 3.7 million inhabitants,[5] it has the highest population within its city limits of any city in the European Union. The city is also one of the states of Germany, being the third smallest state in the country by area. Berlin is surrounded by the state of Brandenburg, and Brandenburg&#39;s capital Potsdam is nearby. The urban area of Berlin has a population of over 4.6 million and is therefore the most populous urban area in Germany.[6][11] The Berlin-Brandenburg capital region has around 6.2 million inhabitants and is Germany&#39;s second-largest metropolitan region after the Rhine-Ruhr region,[5] as well as the fifth-biggest metropolitan region by GDP in the European Union.[12]&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;Berlin&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It used <code>Berlin</code> as title, <code>1</code> as we’re on the first page, and it generated 4 questions, including questions related to the information about the population of the city, where it’s geographically situated, or what its relationship with the country itself (it’s the capital).</p>
<p>For the info box showing the flag and coat of arms of the city:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span> {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;page&#34;</span>: <span style="color:#40a070">1</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;questions&#34;</span>: [
</span></span><span style="display:flex;"><span>      <span style="color:#4070a0">&#34;What does the flag of Berlin look like?&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#4070a0">&#34;What does the coat of arms of Berlin depict?&#34;</span>
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: <span style="color:#4070a0">&#34;The infobox shows the flag of Berlin, which is a horizontal tricolor of red, white, and red, with the city&#39;s coat of arms in the center of the white band. The coat of arms of Berlin is also shown, depicting a black bear standing rampant with its tongue out on a silver shield, topped by a golden crown.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;Flag and Coat of arms&#34;</span>
</span></span><span style="display:flex;"><span>  }<span style="">,</span>
</span></span></code></pre></div><p>It described clearly the orientation and colors of the bands of the German flag, and described the bear on the coat of arms. The title also mentioned the flag and coat of arms. Some embedding models sometimes accept some title metadata that help them better understand the context for calculating vector embeddings. The questions are also quite obvious: what the flag looks like or what is depicted in the coat of arms.</p>
<p>I won’t go through the whole (and long!) document, but you’ll notice that for some short paragraphs with dense information, the LLM can generate a lot more questions than for a paragraph that doesn’t say much (for example with more descriptive but less important details).</p>
<h1 id="what-to-do-with-this-structured-output"><strong>What to do with this structured output?</strong></h1>
<p>As we’ve activated structured output, we had the following JSON structure returned by the LLM:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>     <span style="color:#062873;font-weight:bold">&#34;page&#34;</span>: <span style="color:#40a070">1</span>,
</span></span><span style="display:flex;"><span>     <span style="color:#062873;font-weight:bold">&#34;questions&#34;</span>: [
</span></span><span style="display:flex;"><span>       <span style="color:#4070a0">&#34;question 1&#34;</span>, <span style="color:#4070a0">&#34;question 2&#34;</span><span style="">...</span>
</span></span><span style="display:flex;"><span>     ],
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: <span style="color:#4070a0">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;...&#34;</span>
</span></span><span style="display:flex;"><span>  }<span style="">...</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>With that JSON document, you can go through each chunk, calculate vector embeddings for both questions and the chunk itself, and store the page and title as metadata, in a database supporting vector operations. Then you’re ready to go with your RAG pipeline!</p>
<h1 id="implementation-with-langchain4j"><strong>Implementation with LangChain4j</strong></h1>
<p>This approach is pretty straightforward to implement with any framework, and of course, with <a href="https://docs.langchain4j.dev/">LangChain4j</a> in Java.</p>

<details>
  <summary>Click to view the code and explanations</summary>
  <p>First, describe the data structure that will be used for the structured output:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;A single piece of content&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">PieceOfContent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;Number of the page this text appears in&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>page,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;Title of the section of this text&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>title,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;The text chunk&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>text,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;List of questions that can be answered by this text&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>questions<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, configure the chat model, here we’ll use Gemini 2.5 Flash, and load the document:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(VertexAiGeminiChatModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(SchemaHelper.<span style="color:#4070a0">fromClass</span>(PieceOfContent<span style="color:#666">[]</span>.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Path<span style="color:#bbb"> </span>pathToPdf<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;src/main/resources/Berlin-Wikipedia.pdf&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>pdfBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Files.<span style="color:#4070a0">readAllBytes</span>(pathToPdf);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>encodedPdf<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>String(Base64.<span style="color:#4070a0">getEncoder</span>().<span style="color:#4070a0">encode</span>(pdfBytes));<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now, it’s time to prepare the call to the model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatResponse<span style="color:#bbb"> </span>chatResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">chat</span>(ChatRequest.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>.<span style="color:#4070a0">messages</span>(SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your goal is to analyze long documents for Retrieval Augmented
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Generation (RAG) by splitting the content in pieces.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        The content pieces will be the chunks that will be embedded
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        thanks to an embedding model to calculate vector embedding
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to store in a vector database.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Given a long PDF document, you must extract and format
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        the content so that each piece of content is meaningful
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        as a standalone unit of text.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Each piece of content should be tagged with the page number
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        it appears in, and the title of the section it&#39;s situated in,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        as well as a list of questions whose answers can be provided
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        by this piece of content.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        If there is a table of content, you can use it for adding
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        more context to a given excerpt, or for the section titles,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        but don&#39;t return the table of content as an excerpt.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When you encounter an image, a picture, or a diagram, the piece
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        of content you return should be the description of the image
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        with as much detail as possible.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        If the figure has a caption, use it as the title of the piece
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        of content.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Like for text excerpts, use the title of the current section
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        the image is in in its description.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t create pieces of text for menu, navigation, references,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and other unrelated elements.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        But be sure to create a piece of content
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        for all the text of the document.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Go through ALL the pages of the document.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        The piece of text should be the exact text found in the article.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        DON&#39;T change a single world. DON&#39;T summarize.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;Analyze the following document:&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>PdfFileContent.<span style="color:#4070a0">from</span>(PdfFile.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">base64Data</span>(encodedPdf)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">mimeType</span>(<span style="color:#4070a0">&#34;application/pdf&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>()))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>responseText<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatResponse.<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>PieceOfContent<span style="color:#666">[]</span><span style="color:#bbb"> </span>piecesOfContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Gson().<span style="color:#4070a0">fromJson</span>(responseText,<span style="color:#bbb"> </span>PieceOfContent<span style="color:#666">[]</span>.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We passed the prompt we talked about, and the PDF file of the Wikipedia page. The model then returns the JSON structure that we unmarshal into an array of our record.</p>
</details>

<h1 id="conclusion"><strong>Conclusion</strong></h1>
<p>Taking advantage of a large language model like Gemini allows you to avoid going through the chunking in your own way, and instead rely on the LLM to do semantic chunking for you. You can read more about Retrieval Augmented Generation in some of my <a href="https://glaforge.dev/tags/retrieval-augmented-generation/">previous articles</a> on the topic.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Advanced RAG — Hypothetical Question Embedding</title><link>https://glaforge.dev/posts/2025/07/06/advanced-rag-hypothetical-question-embedding/</link><pubDate>Sun, 06 Jul 2025 15:57:28 +0200</pubDate><guid>https://glaforge.dev/posts/2025/07/06/advanced-rag-hypothetical-question-embedding/</guid><description>&lt;p>In the first article of this Advanced RAG series, I talked about an approach I called
&lt;a href="https://glaforge.dev/posts/2025/02/25/advanced-rag-sentence-window-retrieval/">sentence window retrieval&lt;/a>,
where we calculate vector embeddings per sentence, but the chunk of text returned
(and added in the context of the LLM) actually contains also surrounding sentences
to add more context to that embedded sentence.
This tends to give a better vector similarity than the whole surrounding context.
It is one of the techniques I&amp;rsquo;m covering in my talk on &lt;a href="https://glaforge.dev/talks/2024/10/14/advanced-rag-techniques/">advanced RAG techniques&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>In the first article of this Advanced RAG series, I talked about an approach I called
<a href="https://glaforge.dev/posts/2025/02/25/advanced-rag-sentence-window-retrieval/">sentence window retrieval</a>,
where we calculate vector embeddings per sentence, but the chunk of text returned
(and added in the context of the LLM) actually contains also surrounding sentences
to add more context to that embedded sentence.
This tends to give a better vector similarity than the whole surrounding context.
It is one of the techniques I&rsquo;m covering in my talk on <a href="https://glaforge.dev/talks/2024/10/14/advanced-rag-techniques/">advanced RAG techniques</a>.</p>
<p>Today, I&rsquo;d like to cover another technique I often use in <strong>applications which are more Question/Answer focused</strong>,
where users ask questions, to find answers contained in the indexed documents: <strong>Hypothetical Question Embedding</strong>.</p>
<p>This is an approach I first discovered in this article which covers both
<a href="https://pixion.co/blog/rag-strategies-hypothetical-questions-hyde">hypothetical question embedding and hypothetical document embedding</a> (HyDE, which we might cover in another article later on).
Comparing user queries to hypothetical questions is the technique we&rsquo;ll study today.</p>
<h2 id="the-intuition-behind-hypothetical-questions">The intuition behind Hypothetical Questions</h2>
<p>When explaining vector similarity (or distance), we usually say that embedding vectors of user queries are closer to vector embeddings of text chunks that contain the answer to that query.
It&rsquo;s generally true, and that&rsquo;s why simple fixed-size chunking approaches (with overlap) work usually pretty well enough.
However, this naive approach compares questions to text containing potential answers.</p>
<p>&#x1f4a1; Intuitively, wouldn&rsquo;t it be better to <strong>compare user questions to other questions</strong>?
Or to compare an hypothetical answer (even if wrong) to text chunks with the answer?</p>
<p>Let&rsquo;s say you want to embed this chunk of text from the Wikipedia page of Berlin:</p>
<blockquote>
<p>Berlin is the capital and largest city of Germany, by both area and population. With 3.7 million inhabitants, it has the highest population within its city limits of any city in the European Union. The city is also one of the states of Germany, being the third smallest state in the country by area. Berlin is surrounded by the state of Brandenburg, and Brandenburg&rsquo;s capital Potsdam is nearby. The urban area of Berlin has a population of over 4.6 million and is therefore the most populous urban area in Germany. The Berlin-Brandenburg capital region has around 6.2 million inhabitants and is Germany&rsquo;s second-largest metropolitan region after the Rhine-Ruhr region, as well as the fifth-biggest metropolitan region by GDP in the European Union.</p></blockquote>
<p>The idea is to ask an LLM, like <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-pro?utm_campaign=CDR_0x7a40493f_default_b429992869&amp;utm_medium=external&amp;utm_source=blog">Gemini</a>, to generate questions about this paragraph, with a prompt similar to the following:</p>
<blockquote>
<p>Suggest 10 clear questions whose answer could be given by the user provided text.
Don&rsquo;t use pronouns, be explicit about the subjects and objects of the question.</p></blockquote>
<p>You might want to <strong>change the number of questions generated, depending on the length of the chunk of text</strong>,
or if you know that some documents you embed seem to have a higher or lower density of information.
You can also let the LLM figure out on its own how many questions it could ask.</p>
<p>The second sentence of the prompt is critical to avoid the LLM to generate questions with pronouns, like <code>its population is...</code>.
You want <strong><em>fully-qualified</em> named entities</strong>, to have the whole context of the information.</p>
<p>For this paragraph, the LLM could generate 10 questions like the following ones:</p>
<blockquote>
<ol>
<li>What city is the capital of Germany?</li>
<li>What is the population of Berlin within the city limits?</li>
<li>Which city in the European Union has the highest population within its city limits?</li>
<li>What is Berlin&rsquo;s rank by area among the states of Germany?</li>
<li>Which German state surrounds the city of Berlin?</li>
<li>What is the population of the urban area of Berlin?</li>
<li>What is the most populous urban area in Germany?</li>
<li>How many inhabitants does the Berlin-Brandenburg capital region have?</li>
<li>What is Germany&rsquo;s second-largest metropolitan region?</li>
<li>What is the rank of the Berlin-Brandenburg metropolitan region by GDP in the European Union?</li>
</ol></blockquote>
<p>When comparing the user query <code>What is the population of Berlin</code> or <code>How many inhabitants live in Berlin?</code>,
it would match better (higher similarity) with the second generated question: <code>What is the population of Berlin within the city limits?</code>.</p>
<p><strong>When storing the results in the database with vector support, you will have one record per question.</strong>
You will have the vector embedding of each question, associated with the whole paragraph each time.
There&rsquo;s redundancy here, as the chunk of text is repeated as many times as there are questions.
So this is a technique that uses more space.
And it takes also more time (and potentially higher cost) to embed a whole document as you have to call an LLM for each chunk.
But we&rsquo;ll come back to the pros and cons in the following section.</p>
<p><strong>Upon retrieval, the user question is compared to all those generated questions.</strong>
<strong>And at prompt augmentation time, it&rsquo;s the text chunk that is returned, not the generated question.</strong></p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Try it out</span>
      </div>
      <div class="admonition-content">
        <p>If you want to test this idea of <strong>hypothetical question embeddidng</strong>, feel free to go ahead and try this
<a href="https://hypothetical-questions-1029513523185.europe-west1.run.app/">application</a>
I vibe-coded with <a href="https://gemini.google.com/canvas">Gemini Canvas</a> (for scaffolding the UI) and
<a href="https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/?utm_campaign=CDR_0x7a40493f_default_b429992869&amp;utm_medium=external&amp;utm_source=blog">Gemini CLI</a>
(for creating the server app) and deployed to Cloud Run.</p>
<p>You can:</p>
<ul>
<li>enter a chunk of text,</li>
<li>generate hypothetical questions for that chunk,</li>
<li>compare the vector embeddings between the user query, the document, and the hypothetical questions.</li>
</ul>
<p><a href="https://hypothetical-questions-1029513523185.europe-west1.run.app/"><figure>
  <a href="#img-4a7921b283e3307491132080d3fe3aaa">
    <img src="/img/rag/hypothetical-questions-ui.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-4a7921b283e3307491132080d3fe3aaa">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/rag/hypothetical-questions-ui.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
      </div>
    </div><h2 id="hypothetical-question-embedding-vs-fixed-sized-chunk-embedding">Hypothetical Question embedding vs fixed-sized chunk embedding</h2>
<p>What are the pros and cons of each approach?</p>
<h3 id="classical-fixed-sized-chunk-embedding">Classical Fixed-Sized Chunk Embedding</h3>
<p>This is the most straightforward method. You simply split your documents into chunks of a fixed size (e.g., 500 characters) and then create an embedding for each chunk.</p>
<p><strong>Pros: 👍</strong></p>
<ul>
<li><strong>Simplicity and speed</strong>: It&rsquo;s easy to implement and computationally efficient, making it great for large datasets.</li>
<li><strong>Predictable size</strong>: Uniform chunk sizes make it easy to manage and process without resource spikes.</li>
</ul>
<p><strong>Cons: 👎</strong></p>
<ul>
<li><strong>Context splitting</strong>: This method can cut sentences or even words in half, leading to a loss of meaning and context. However this is generally mitigated thanks to using an overlap between chunks.</li>
<li><strong>Loss of coherence</strong>: Arbitrarily splitting text can make it difficult for the model to understand the overall narrative or argument.</li>
<li><strong><em>&ldquo;Lost in the middle&rdquo;</em> problem</strong>: Important information can be lost if it&rsquo;s located in the middle of a long document, as the smaller, more focused chunks might not capture the broader context. It can be mitigated with storing and returning a wider surrounding context than what was embedded (i.e. the technique from the <a href="https://glaforge.dev/posts/2025/02/25/advanced-rag-sentence-window-retrieval/">first article</a> of this series).</li>
</ul>
<h3 id="hypothetical-question-embedding">Hypothetical Question Embedding</h3>
<p>With this technique, you use a language model to generate questions for each chunk of your document. Then, you embed these questions instead of the document chunks themselves. When a user asks a question, the system compares their query to the embedded hypothetical questions. But when augmenting the context of the LLM with the results of the vector search, you actually return the inital text from which questions were extracted.</p>
<p><strong>Pros: 👍</strong></p>
<ul>
<li><strong>Improved alignment</strong>: You are comparing a question to a question, which can lead to better semantic matching than comparing a question to a document chunk. This can significantly improve retrieval accuracy.</li>
<li><strong>Addresses the <em>&ldquo;Lost in the Middle&rdquo;</em> problem</strong>: By generating questions for all parts of a document, you&rsquo;re more likely to retrieve relevant information regardless of where it is.</li>
</ul>
<p><strong>Cons: 👎</strong></p>
<ul>
<li><strong>Increased index size</strong>: Generating multiple questions per document chunk means your vector index can become much larger, potentially slowing down search and increasing storage costs. This is because you store the chunk of text as many times as the number of questions which were generated.</li>
<li><strong>Upfront computational cost</strong>: You need to use a language model to generate all the hypothetical questions, which can be time-consuming and expensive if you use hosted models that you pay by the token.</li>
<li><strong>Quality depends on generated questions</strong>: The effectiveness of this method is entirely dependent on the quality of the generated questions. If the language model fails to generate relevant questions, the retrieval will suffer.</li>
<li><strong>Non-deterministic questions</strong>: When you re-index your documents (for example because there was an update) even when using the same embedding model, as this approach relies on an LLM, the new batch of questions might be quite different, as the LLM won&rsquo;t necessarily generate the same questions each time.</li>
</ul>
<h2 id="implementation-details">Implementation details</h2>
<p>The Hypothetical Question approach can be implemented in any language or framework.
But for the sake of this article, I&rsquo;ll be using <a href="https://docs.langchain4j.dev/">LangChain4j</a> in Java.</p>
<p>You can have a look at the whole <a href="https://github.com/datastaxdevs/conference-2024-devoxx/blob/main/devoxx-rag-naive-to-advanced/src/test/java/devoxx/rag/_3_advanced_rag_ingestion/_37_hypothetical_questions_embedding.java">source code</a> of this implementation, but I&rsquo;ll explain in details below what&rsquo;s happening.</p>
<h3 id="at-ingestion-time">At ingestion time</h3>
<p>Let&rsquo;s ingest the documents, generate questions, and calculate the vector embeddings.</p>

<details>
  <summary>Click to view the code and explanations</summary>
  <p>The first thing to do is to load the document (I saved the Wikipedia article as a text file):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Document<span style="color:#bbb"> </span>documentAboutBerlin<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>FileSystemDocumentLoader.<span style="color:#4070a0">loadDocument</span>(<span style="color:#4070a0">&#34;berlin.txt&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextDocumentParser());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s configure the large language model, here with Gemini from Vertex AI,
using a response schema, to force the model to return a JSON array of hypothetical question strings:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(MODEL_GEMINI_FLASH)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxRetries</span>(5)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">ARRAY</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setItems</span>(Schema.<span style="color:#4070a0">newBuilder</span>().<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>).<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>QuestionParagraph</code> record will hold a pair of question and the current text paragraph whose content can answer the question:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">QuestionParagraph</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>question,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>TextSegment<span style="color:#bbb"> </span>paragraph<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The question/paragraph pairs are held in a list:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>QuestionParagraph<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>allQuestionParagraphs<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>I&rsquo;m splitting the document into paragraphs, with paragraphs no longer than 2000 characters, and with an overlap of 100 characters, when the threshold is hit:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>DocumentByParagraphSplitter<span style="color:#bbb"> </span>splitter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>DocumentByParagraphSplitter(2000,<span style="color:#bbb"> </span>100);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>paragraphs<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>splitter.<span style="color:#4070a0">split</span>(documentAboutBerlin);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now comes the interesting part!
For each paragraph, I ask the LLM to generate 10 questions, and I store the question/paragraphs pairs in the <code>allQuestionParagraphs</code> list:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(TextSegment<span style="color:#bbb"> </span>paragraphSegment<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>paragraphs)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ChatResponse<span style="color:#bbb"> </span>aiResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Suggest 10 clear questions whose answer could be given
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            by the user provided text.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Don&#39;t use pronouns, be explicit about the subjects
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            and objects of the question.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>UserMessage.<span style="color:#4070a0">from</span>(paragraphSegment.<span style="color:#4070a0">text</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#666">[]</span><span style="color:#bbb"> </span>questions<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>gson.<span style="color:#4070a0">fromJson</span>(aiResult.<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>(),<span style="color:#bbb"> </span>String<span style="color:#666">[]</span>.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0;<span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">&lt;</span><span style="color:#bbb"> </span>questions.<span style="color:#4070a0">length</span>;<span style="color:#bbb"> </span>i<span style="color:#666">++</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>question<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>questions<span style="color:#666">[</span>i<span style="color:#666">]</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>allQuestionParagraphs.<span style="color:#4070a0">add</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>QuestionParagraph(question,<span style="color:#bbb"> </span>paragraphSegment));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The other important piece is to calculate the vector embeddings of the questions,
but save the text of the paragraph in the embedding vector store:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>embeddedSegments<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>allQuestionParagraphs.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">map</span>(questionParagraph<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>TextSegment.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>questionParagraph.<span style="color:#4070a0">question</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Metadata().<span style="color:#4070a0">put</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>PARAGRAPH_KEY,<span style="color:#bbb"> </span>questionParagraph.<span style="color:#4070a0">paragraph</span>().<span style="color:#4070a0">text</span>())))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toList</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Embedding<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>embeddings<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>embeddingModel.<span style="color:#4070a0">embedAll</span>(embeddedSegments).<span style="color:#4070a0">content</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>embeddingStore.<span style="color:#4070a0">addAll</span>(embeddings,<span style="color:#bbb"> </span>embeddedSegments);<span style="color:#bbb">
</span></span></span></code></pre></div><p>I&rsquo;m using the <code>text-embedding-004</code> model (configured elsewhere in the code).
You can use also <code>text-embedding-005</code> or the new <code>gemini-embedding-01</code> model,
but the latter lacks batching for now, so you can&rsquo;t embed several text segments at once.</p>
<p>The key aspect to pay attention to is that I store extra metadata: the actual paragraph.
But it&rsquo;s really the hypothetical question whose embedding vector is calculated.
But the metadata will be useful at retrieval time, to inject the paragraph in the LLM prompt.</p>
</details>

<h3 id="at-retrieval-time">At retrieval time</h3>
<p>Now that the ingestion is done, let&rsquo;s have a look at the retrieval phase, when users ask questions.</p>

<details>
  <summary>Click to view the code and explanations</summary>
  <p>Let&rsquo;s use the low-level components of LangChain4j to do the search:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>EmbeddingSearchResult<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>searchResults<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>embeddingStore.<span style="color:#4070a0">search</span>(EmbeddingSearchRequest.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxResults</span>(4)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">minScore</span>(0.<span style="color:#4070a0">7</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">queryEmbedding</span>(embeddingModel.<span style="color:#4070a0">embed</span>(queryString).<span style="color:#4070a0">content</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>This means we&rsquo;re calculating the vector embedding of <code>queryString</code> (the user&rsquo;s question), and compare it with the other vectors stored in the database. We want to retrieve only 4 results with a minimum similarity score of 0.7 (value ranging between 0 and 1).</p>

    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Important Remark</span>
      </div>
      <div class="admonition-content">
        <p>A very important remark: be sure to <strong>use the same embedding model for both ingestion and retrieval</strong>.
Otherwise the vector embedding values will likely be drastically different, and will give totally garbage results.</p>
      </div>
    </div><p>Now it&rsquo;s time to do the prompt augmentation, by injecting all the paragraphs associated with the closest vectors of the query:</p>
<p>Concatenate all the relevant paragraphs:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>concatenatedExtracts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>searchResults.<span style="color:#4070a0">matches</span>().<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">map</span>(match<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>match.<span style="color:#4070a0">embedded</span>().<span style="color:#4070a0">metadata</span>().<span style="color:#4070a0">getString</span>(PARAGRAPH_KEY))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">distinct</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">collect</span>(Collectors.<span style="color:#4070a0">joining</span>(<span style="color:#4070a0">&#34;\n---\n&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\n---\n&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\n---\n&#34;</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>And augment the prompt with those extracts:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>UserMessage<span style="color:#bbb"> </span>userMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>PromptTemplate.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You must answer the following question:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    {{question}}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Base your answer on the following documentation extracts:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    {{extracts}}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>).<span style="color:#4070a0">apply</span>(Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;question&#34;</span>,<span style="color:#bbb"> </span>queryString,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;extracts&#34;</span>,<span style="color:#bbb"> </span>concatenatedExtracts<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)).<span style="color:#4070a0">toUserMessage</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>At the end, it&rsquo;s time to ask the LLM to formulate a response with this augmented prompt:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">chat</span>(userMessage).<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>And voilà!</p>
</details>

<h2 id="fixed-chunk-embedding-or-hypothetical-question-embedding">Fixed chunk embedding or Hypothetical Question Embedding?</h2>
<p>The question I often get is to know which technique should be used: fixed chunking, hypothetical question embedding, or another?
I&rsquo;ll reply with the typical consultant answer: <strong>it depends!</strong></p>
<p>That&rsquo;s the point where I&rsquo;ll tell you that <strong>evaluation is key!</strong>
Hypothetical Question embedding typically work better for applications that are indeed Question/Answer focused.
If users ask questions about their data (let&rsquo;s say, an HR chatbot to ask questions about the vacation policy)
this technique works well.
But maybe for applications where the semantic search is more about finding similar documents, this might not yield the same kind of performance.</p>
<p>It&rsquo;s important to run evaluation on your data, with typical user queries, and check which technique yields better results.
We might cover evaluation in another article, later on.</p>

    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Learn More</span>
      </div>
      <div class="admonition-content">
        <p>If you want to learn more about evaluation techniques, be sure to check out the <a href="https://atamel.dev/">articles</a>
from my colleague <a href="https://x.com/meteatamel">Mete Atamel</a>.</p>
      </div>
    </div><h2 id="going-forward">Going forward</h2>
<ul>
<li>Play with the <a href="https://hypothetical-questions-1029513523185.europe-west1.run.app/">hypothetical question application</a>
I deployed on <a href="https://cloud.google.com/run?utm_campaign=CDR_0x7a40493f_default_b429992869&amp;utm_medium=external&amp;utm_source=blog">Cloud Run</a> to see the impact on vector similarity.</li>
<li>Have another read of my article on the
<a href="https://glaforge.dev/posts/2025/02/25/advanced-rag-sentence-window-retrieval/">sentence window retrieval</a> technique,
to see if it fits better with the kind of documents you have in your corpus.</li>
<li>You can also watch the <a href="https://glaforge.dev/talks/2024/10/14/advanced-rag-techniques/">talk on advanced RAG techniques</a>,
as well as the slides, to see all the other techniques that can be combined.</li>
<li>But never forget to prepare evaluations on your data, with typical user queries, to compare which techniques yield better results!</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Expanding ADK AI agent capabilities with tools</title><link>https://glaforge.dev/posts/2025/06/15/expanding-ai-agent-capabilities-with-tools/</link><pubDate>Sun, 15 Jun 2025 19:12:27 +0200</pubDate><guid>https://glaforge.dev/posts/2025/06/15/expanding-ai-agent-capabilities-with-tools/</guid><description>&lt;p>In a nutshell, the &lt;strong>AI agent equation&lt;/strong> is the following:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>&lt;strong>AI Agent = LLM + Memory + Planning + Tool Use&lt;/strong>&lt;/em>&lt;/p>&lt;/blockquote>
&lt;p>AI agents are nothing without tools!
And they are actually more than mere Large Language Model calls.
They require some memory management to handle the context of the interactions (short term, long term, or contextual information like in the
&lt;a href="https://glaforge.dev/tags/retrieval-augmented-generation/">Retrieval Augmented Generation approach&lt;/a>.
Planning is important (with variations around the Chain-of-Thought prompting approach, and LLM with reasoning or thinking capabilities) for an agent to realize its tasks.&lt;/p></description><content:encoded>
<![CDATA[<p>In a nutshell, the <strong>AI agent equation</strong> is the following:</p>
<blockquote>
<p><em><strong>AI Agent = LLM + Memory + Planning + Tool Use</strong></em></p></blockquote>
<p>AI agents are nothing without tools!
And they are actually more than mere Large Language Model calls.
They require some memory management to handle the context of the interactions (short term, long term, or contextual information like in the
<a href="https://glaforge.dev/tags/retrieval-augmented-generation/">Retrieval Augmented Generation approach</a>.
Planning is important (with variations around the Chain-of-Thought prompting approach, and LLM with reasoning or thinking capabilities) for an agent to realize its tasks.</p>
<p>But for agents to be useful and to be able to sense or act upon their environment, the need access to tools.
Generally speaking, <em>tool use</em> is about leveraging LLM&rsquo;s <em>function calling</em> ability, to understand when it needs to request some kind of function to be called to proceed further in its next actions or next steps.</p>
<p>In my previous articles about <a href="https://google.github.io/adk-docs/">ADK</a>, I guided you through the
<a href="https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/">creation of your first AI agent with ADK for Java</a>,
and I even shared a <a href="https://glaforge.dev/posts/2025/05/27/adk-java-github-template/">Github project template</a> to help you get started faster.
But today, I want to explore with you the concept of tools, and what tools are at your disposal when <strong>creating AI agents in Java with ADK</strong>.</p>
<h2 id="built-in-tools">Built-in tools</h2>
<p>ADK comes with a handful of very useful built-in tools:</p>
<ul>
<li>a Google Search tool,</li>
<li>a Python code executor,</li>
<li>an artifact service to store and load files.</li>
</ul>
<h3 id="circumventing-llms-cut-off-date-with-google-search">Circumventing LLM&rsquo;s cut-off date with Google Search</h3>
<p>LLMs&rsquo; knowledge is as recent as the last information of the corpus of training data they were trained on.
For example, if you asked an LLM who won the Roland Garros tennis tournament, since the finals ended just a week ago,
it wouldn&rsquo;t be able to tell you who won the 2025 edition.
However, if you give an LLM access to a search engine, it can answer that question with <em>grounded</em> facts.</p>
<p>To do that with ADK, you need to add the <code>GoogleSearchTool</code> tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Given</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;a helpful assistant who can search in Google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful assistant
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        who knows how to search in Google Search.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Today is 2025-06-15.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GoogleSearchTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// When</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>askAgent(agent,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Who&#39;s the man won Roland Garos 2025?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Then</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>assertThat(events.<span style="color:#4070a0">get</span>(0).<span style="color:#4070a0">content</span>().<span style="color:#4070a0">get</span>().<span style="color:#4070a0">text</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">containsIgnoringCase</span>(<span style="color:#4070a0">&#34;Alcaraz&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The Google Search tool is also very useful if you want to build some kind of <em>deep research</em> agent that is able to search the web to collect key information to create complex reports.</p>
<h3 id="executing-code-when-advanced-calculations-or-algorithms-are-needed">Executing code when advanced calculations or algorithms are needed</h3>
<p>LLMs are notoriously bad at math or letter games, and at unrolling complex algorithms needed for reasoning purposes (like logic puzzles).
However, they are pretty good at generating code.
For math or algorithms, they are totally capable of generating the right piece of code that could solve the task at hand.
So if you give an LLM the ability to execute some code it generates, and examine the output of that execution, it&rsquo;s going to be able to understand the problem and give a correct answer.</p>
<p>ADK gives you access to the <code>BuiltInCodeExecutionTool</code> tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Given</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;a helpful assistant that knows how to code&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful assistant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Today is 2025-06-10.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>BuiltInCodeExecutionTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// When</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>askAgent(agent,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;How much is Fibonacci(12) + Hackermann(3,3)?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Then</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Content<span style="color:#bbb"> </span>content<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>events.<span style="color:#4070a0">get</span>(0).<span style="color:#4070a0">content</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>assertThat(content.<span style="color:#4070a0">text</span>()).<span style="color:#4070a0">contains</span>(<span style="color:#4070a0">&#34;205&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the example above, Gemini will write some Python code, and execute it inside a sandboxed Python interpreter, to give you the final answer.</p>
<h3 id="tool-to-save-and-load-artifacts">Tool to save and load artifacts</h3>
<p>Last built-in tool I&rsquo;d like to mention briefly: the <code>LoadArtifactsTool</code> tool, to deal with <a href="https://google.github.io/adk-docs/artifacts/">artifacts</a>
(although they&rsquo;d deserve their own article too).</p>
<p>Artifacts are named, versioned text or binary data associated with a user session or associated with a user across sessions.
Such <em>files</em> can be persisted via the artifact service (there&rsquo;s even a Google Cloud Storage artifact service for long term storage).</p>
<p>Artifacts are accessible via methods like <code>saveArtifact()</code>, <code>loadArtifact()</code>, or <code>listArtifacts()</code> on objects like <code>CallbackContext</code> (when adding callbacks to your agents), or <code>ToolContext</code> (when adding tools to your agents in methods taking a <code>ToolContext</code> parameter).
Artifacts can also be accessed via the system instructions used to declare your agent.</p>
<p>I won&rsquo;t dive into details today, but for the sake of completeness, here&rsquo;s how you can configure the tool and mention artifacts in the agent system instructions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-movie-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        a helpful assistant who knows
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        about some rather unknown or obscure movies
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful movie assistant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When asked questions about actors in a movie,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        forget about all intrinsic knowledge, and
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        lookup the details in the artifact {artifact.movies.txt}.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LoadArtifactsTool())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="custom-tools">Custom tools</h2>
<p>When you need your own piece of logic to help your AI agent, you can create custom tools, via the <code>FunctionTool</code> class.
Custom tools are just regular methods, but with a twist: with some carefully crafted annotations to describe the tool to help LLMs understand what this tool can do.</p>
<p>Let&rsquo;s give the agent access to a <code>moonPhase</code> method inside the <code>ToolsTest</code> class to compute the phase of the moon for a given date:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Given</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;a helpful assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful assistant who knows about the moon.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Today is 2025-06-15.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(FunctionTool.<span style="color:#4070a0">create</span>(ToolsTest.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;moonPhase&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// When</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>askAgent(agent,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;What&#39;s the moon phase today?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Then</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>text<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>events.<span style="color:#4070a0">get</span>(2).<span style="color:#4070a0">parts</span>().<span style="color:#4070a0">get</span>().<span style="color:#4070a0">get</span>(0).<span style="color:#4070a0">text</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>assertThat(text).<span style="color:#4070a0">containsIgnoringCase</span>(<span style="color:#4070a0">&#34;full moon&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>And now let&rsquo;s see what the <code>moonPhase</code> method does (with a hard-coded answer):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Schema</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;get the moon phase for a given date&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">moonPhase</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Schema</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;date&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;the date for which to get the moon phase&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>date)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;moon-phase&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;full moon&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I annotated the <code>moonPhase()</code> method with a <code>@Schema</code> with a description,
as well as the <code>date</code> parameter with both a description and a name.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>This is very important to properly document your custom tool
as LLMs will understand this information and that will help them
figure out how to find the right method to invoke, and which parameters to pass it.</p>
      </div>
    </div><p>As of the time of this writing, the <code>0.1.0</code> release of ADK for Java supports <code>static</code> methods,
but in an upcoming version, it&rsquo;ll be possible to use instance methods as well.</p>
<p>Also note that it is mandatory to return a <code>Map</code>.
The reason is that you either return some kind of complex JSON object (that can be transparently un/marshalled),
or you return a map with some <code>status</code> field in addition to the normal return object,
to help the LLM understand if the execution was successful or not.
For example: <code>{&quot;status&quot;: &quot;success&quot;, &quot;moon-phase&quot;: &quot;full moon&quot;}</code>.</p>
<h3 id="what-about-multimodal-tools">What about multimodal tools?</h3>
<p>Since tool support is done via LLM&rsquo;s function calling capability, it&rsquo;s also limited by it!
Currently, I&rsquo;m not aware of LLMs that are able to generate function calls that contain non-textual information, such as images, videos, etc.
Fortunately, there&rsquo;s a way to circumvent this limitation, thanks to ADK&rsquo;s <code>ToolContext</code>.</p>
<p>Let&rsquo;s say you want to leverage LLM&rsquo;s multimodal ability, by looking at a picture of the moon, and guess the phase of the moon depicted in that image.
Function calling can&rsquo;t pass the image directly, so let&rsquo;s see how you can access the user&rsquo;s full multimodal message via the <code>ToolContext</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Given</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;a helpful assistant who can analyze pictures of the moon&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful assistant who knows about the moon.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When asked a question about the moon, or pictures of the moon,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you MUST call the `moonPhaseFromImage` function.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(FunctionTool.<span style="color:#4070a0">create</span>(ToolsTest.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;moonPhaseFromImage&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;re still creating a custom tool with <code>FunctionTool.create()</code> as before.
However, our method definition will have an additional parameter: an instance of <code>ToolContext</code>.
Note that it should be named <code>toolContext</code>, otherwise ADK won&rsquo;t be happy.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Schema</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;get the moon phase by analyzing pictures&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">moonPhaseFromImage</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Schema</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;toolContext&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ToolContext<span style="color:#bbb"> </span>toolContext)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Optional<span style="color:#666">&lt;</span>List<span style="color:#666">&lt;</span>Part<span style="color:#666">&gt;&gt;</span><span style="color:#bbb"> </span>optionalParts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>toolContext.<span style="color:#4070a0">userContent</span>().<span style="color:#4070a0">flatMap</span>(Content::parts);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(optionalParts.<span style="color:#4070a0">isPresent</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>List<span style="color:#666">&lt;</span>Part<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imageParts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>optionalParts.<span style="color:#4070a0">get</span>().<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">filter</span>(part<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>part.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">isPresent</span>()).<span style="color:#4070a0">toList</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(imageParts.<span style="color:#4070a0">size</span>()<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span>1)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Part<span style="color:#bbb"> </span>imagePart<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>imageParts.<span style="color:#4070a0">get</span>(0);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>imageBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>imagePart.<span style="color:#4070a0">inlineData</span>().<span style="color:#4070a0">get</span>().<span style="color:#4070a0">data</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// do something with the image bytes...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// make a normal multimodal LLM call</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// and return the result</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;moon-phase&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;half moon&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;moon-phase&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;unknown&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The key line to look at (in addition to the <code>toolContext</code> parameter in the signature of the method) is the <code>toolContext.userContent()</code> call.
It gives you access to the <code>Part</code>s of the <code>Content</code> object which represents the user request.</p>
<p>In this code snippet above, we just retrieve the bytes of the uploaded image, and we&rsquo;re faking doing something with them.
But that&rsquo;s the place where you could make an LLM invocation to ask to analyze the image, and guess the phase of the moon depicted in the image.</p>
<h2 id="long-running-custom-tools">Long running custom tools</h2>
<p>So far, I&rsquo;ve talked about tools that are pretty much synchronous in nature, as they usually answer quite rapidly.
But what about situations where you have <strong>long-running workflows</strong> that take several hours or even days to run?</p>
<p>Or what about scenarios where there&rsquo;s the need for a <strong>human in the loop</strong> to validate some action, like a manager who needs to accept or reject an expense report from an employee? That&rsquo;s where <strong>long-running custom tools</strong> come in handy.</p>
<p>ADK offers <code>LongRunningFunctionTool</code>s.
In terms of API, they are exactly like <code>FunctionTool</code>s.
It&rsquo;s just that the framework knows the function will acknowledge the reception of the request, but the full completion of the request may happen at a later time.</p>
<p>Something that confused me initially was that working with LLMs is very <em>request / response</em> oriented,
in the sense that there&rsquo;s always an input from a user, that leads to an output from the LLM.
And the conversation goes on and so forth.</p>
<p>What bothered me was the fact that I didn&rsquo;t know what would happen when we actually receive the final completion answer from the LLM.
Or even how will we receive that completion status?
Well, the thing is that we need to shift our mindset from the request / response turns approach,
and instead think of the fact that ADK is more like an <em>event loop</em>, which doesn&rsquo;t need a response to always follow a request,
but events can flow in and out, from the user, or from the system itself, in any order.</p>
<p>Let&rsquo;s configure a long running function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Given</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;a helpful assistant who can execute workflows&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;You&#39;re a helpful assistant.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(LongRunningFunctionTool.<span style="color:#4070a0">create</span>(ToolsTest.<span style="color:#4070a0">class</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                          </span><span style="color:#4070a0">&#34;executeWorkflow&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This looks just like a normal <code>FunctionTool</code> declaration.</p>
<p>Now let&rsquo;s send a&hellip; <em>recipe</em> workflows!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// When</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>askAgent(agent,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Execute the following workflow:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - peel the potatoes
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - cut the potatoes in dice
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - put olive oil in the pan
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - heat pan
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - put the potato dices
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - stir regularly
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    - cook for 10 minutes
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Baking some potatoes can take some time, so you&rsquo;re not going to eat your cooked potatoes immediately!</p>
<p>Let&rsquo;s have a look at what our <code>executeWorkflow</code> method does:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Schema</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    execute a long running workflow made of
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    several steps explained in natural language
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>Object<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">executeWorkflow</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Schema</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;workflowDescription&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;description of the workflow to execute&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>workflowDescription,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Schema</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;workflowSteps&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;a list of workflow steps&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>workflowSteps)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;status&#34;</span>,<span style="color:#bbb"> </span>LongRunningOperation.<span style="color:#4070a0">Status</span>.<span style="color:#4070a0">STARTED</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;longRunningOperation&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LongRunningOperation(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>LongRunningOperation.<span style="color:#4070a0">Status</span>.<span style="color:#4070a0">STARTED</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>workflowDescription,<span style="color:#bbb"> </span>workflowSteps));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>My <code>executeWorkflow</code> method actually returns immediately, to acknowledge the reception of the request.
We can imagine here that we&rsquo;re sending the steps (here a list of strings) to some workflow execution engine.
As return type of the method, I&rsquo;ve decided to return a <code>status</code> indicating that the long running operation has started.
And I also return (in the same map) some <code>LongRunningOperation</code> object with the status, the workflow description, and the steps.</p>
<p>I&rsquo;ve defined this <code>LongRunningOperation</code> as a record, and the status itself as a <code>Status</code> <code>enum</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">LongRunningOperation</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>LongRunningOperation.<span style="color:#4070a0">Status</span><span style="color:#bbb"> </span>status,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>description,<span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>steps)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">enum</span><span style="color:#bbb"> </span>Status<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>STARTED,<span style="color:#bbb"> </span>FINISHED,<span style="color:#bbb"> </span>ERROR<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>But you can, of course, return some more complex object that contains an ID to be able to identify the workflow execution.
So that we know which execution completed.</p>
<p>At this point, the LLM used by ADK will reply to the user to acknowledge the reception of the request, and the start of the workflow.
Later on, an event should be sent back via ADK somehow, to notify your application of the completion of the workflow,
in a more event-oriented approach compared to the usual request/response approach of LLM conversations.</p>
<p>The example above is a dummy one, as we&rsquo;re not really executing a long running operation, and we&rsquo;re not going to receive an event upon completion.
So my idea is that I&rsquo;ll come back with an article later on that will dive deeper into long running operations and human in the loop scenarios,
as I believe we need a full article and complete demonstration to illustrate this concept more thoroughly.</p>
<h2 id="an-agent-as-a-tool">An agent as a tool</h2>
<p>So far, we talked about built-in tools, and custom tools, but there&rsquo;s another kind of tool which is quite powerful, and which turn another agent into a tool itself!</p>
<p>Let&rsquo;s come back to our moon phase example.
First, let&rsquo;s define the agent that has access to the <code>moonPhase()</code> function.
It&rsquo;ll be the agent that will serve as a tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>moonAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;moon-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Agent that knows about the moon&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You know everything about the moon!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Today is 2025-06-15.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When asked about the phase of the moon,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        call the `moonPhase` tool with the current date as parameter.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>FunctionTool.<span style="color:#4070a0">create</span>(ToolsTest.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;moonPhase&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now let&rsquo;s see how we can turn this sidekick agent into a tool, via the <code>AgentTool.create()</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>mainAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;a helpful assistant who knows about the moon&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful assistant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When a question about the moon is asked,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        ask the question to the `moon-agent` tool.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(AgentTool.<span style="color:#4070a0">create</span>(moonAgent))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// When</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>askAgent(mainAgent,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;What&#39;s the moon phase today?&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>I&rsquo;m not showing all the <code>assert</code> statements, but there will be 3 events:
a function execution request addressed to the <code>moon-agent</code>, a function response from the <code>moon-agent</code>, and the final answer by the LLM who will reformulate the function response into a nice human readable answer.</p>
<p>I haven&rsquo;t talked about the various <a href="https://google.github.io/adk-docs/agents/workflow-agents/">agent flows supported by ADK</a>,
like <em>sub-agents</em>, <em>sequential</em> agents, <em>loop</em> agents, and <em>parallel</em> agents, but <strong>agent as tool</strong> is a very powerful pattern for creating more complex agents composed or more specific agents.</p>
<p>Generally, a multi-agent system will be more powerful and more reliable than a big monolithic agent, when tasks can be split and shared among more specialized agents.
I&rsquo;ll come back to this later, in a subsequent article.</p>

    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>Today, a limitation of Gemini is that you can&rsquo;t use a function call and a built-in tool at the same time.
ADK, when using Gemini as the underlying LLM, takes advantage of Gemini&rsquo;s built-in ability to do Google searches, and uses function calling to invoke your custom ADK tools.
So agent tools can come in handy, as you can have a main agent, that delegates live searches to a search agent that has the <code>GoogleSearchTool</code> configured,
and another tool agent that makes use of a custom tool function.</p>
<p>Usually, this happens when you get a mysterious error like this <a href="https://github.com/google/adk-python/issues/134">one</a> (reported against ADK for Python):
<code>{'error': {'code': 400, 'message': 'Tool use with function calling is unsupported', 'status': 'INVALID_ARGUMENT'}}</code>.
This means that you can&rsquo;t use a built-in tool and function calling at the same time in the same agent.
The workaround, then, is to decompose the problem into multiple agents, and taking advantage of agent tools.</p>
      </div>
    </div><h2 id="calling-mcp-tools">Calling MCP tools</h2>
<p>Last but not least, let&rsquo;s finish the round of tools with the most trendy one: <strong>MCP</strong> tools (<a href="https://glaforge.dev/tags/model-context-protocol/">Model Context Protocol</a>)!</p>
<p>Last week, I wrote about how to
<a href="https://glaforge.dev/posts/2025/06/09/building-an-mcp-server-with-quarkus-and-deploying-on-google-cloud-run/">create an MCP SSE server with Quarkus</a>,
where I exposed a couple of MCP tools that allow you to know the phase of the moon today or at a custom date — hence the theme again today with the moon.
Let&rsquo;s see how we can configure an agent to use this tool.</p>
<p>It is possible to use Server-Sent Event (SSE for short), or STDIO (standard-in / standard-out) protocols.
The moon phases MCP tool I created the other day was an SSE one.</p>
<p>The first thing to do is to configure it by giving the endpoint of the MCP server:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>SseServerParameters<span style="color:#bbb"> </span>sseParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>SseServerParameters.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">url</span>(<span style="color:#4070a0">&#34;https://moonphases-1234567890.europe-west1.run.app/mcp/sse&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then you can list (and potentially filter manually) the tools available:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>McpToolset.<span style="color:#4070a0">McpToolsAndToolsetResult</span><span style="color:#bbb"> </span>toolsAndToolsetResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>McpToolset.<span style="color:#4070a0">fromServer</span>(sseParams).<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>McpTool<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>moonPhasesTools<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>toolsAndToolsetResult.<span style="color:#4070a0">getTools</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>And now you can configure the agent with the list of MCP tools you want to give it access:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;helpful-assistant&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;a helpful assistant who knows about the moon&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a helpful assistant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(moonPhasesTools)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>And that&rsquo;s it! Pretty easy, right?</p>
<h2 id="summary">Summary</h2>
<p>In this article, we explored the concept of tools in AI agents, specifically in the context of ADK for Java.
First, we looked at the <strong>built-in tools</strong>, like Google Search, Python code execution, or the artifacts service.
Next, we explored <strong>custom tools</strong>, including <strong>long-running tools</strong>, or also how to <strong>handle multimodal requests</strong> thanks to the tool context.
We discovered the <strong>agent as tool</strong> concept, as a smart agent can be a tool itself for another agent.
Lastly, we also learned about remote <strong>MCP server tools</strong>.</p>
<p>In upcoming articles in this series, we&rsquo;ll dive deeper into some of those tools, and we&rsquo;ll build more complex use cases.
So stay tuned for the next episode!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Building an MCP server with Quarkus and deploying on Google Cloud Run</title><link>https://glaforge.dev/posts/2025/06/09/building-an-mcp-server-with-quarkus-and-deploying-on-google-cloud-run/</link><pubDate>Mon, 09 Jun 2025 13:01:03 +0200</pubDate><guid>https://glaforge.dev/posts/2025/06/09/building-an-mcp-server-with-quarkus-and-deploying-on-google-cloud-run/</guid><description>&lt;p>As I’m contributing to &lt;a href="https://github.com/google/adk-java">ADK&lt;/a> (Agent Development Kit) for Java, and &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a> (the LLM orchestration framework) I interact with &lt;a href="https://modelcontextprotocol.io/introduction">MCP&lt;/a> (Model Context Protocol) servers and tools to further expand the capabilities of my LLMs.&lt;/p>
&lt;p>Recently, I showed how to &lt;a href="https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/">vibe-code an MCP server using Micronaut&lt;/a>. You know I usually talk about &lt;a href="https://micronaut.io/">Micronaut&lt;/a>, but this time, I wanted to experiment with Quarkus, and in particular with its built-in support for &lt;a href="https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html">implementing MCP servers&lt;/a>.&lt;/p>
&lt;h2 id="getting-started-with-quarkus-mcp-support">Getting started with Quarkus’ MCP support&lt;/h2>
&lt;p>I created a brand new Quarkus project from IntelliJ IDEA, with its Quarkus template, and I added a couple key dependencies for JSON marshalling, but even more important, for the MCP support:&lt;/p></description><content:encoded>
<![CDATA[<p>As I’m contributing to <a href="https://github.com/google/adk-java">ADK</a> (Agent Development Kit) for Java, and <a href="https://docs.langchain4j.dev/">LangChain4j</a> (the LLM orchestration framework) I interact with <a href="https://modelcontextprotocol.io/introduction">MCP</a> (Model Context Protocol) servers and tools to further expand the capabilities of my LLMs.</p>
<p>Recently, I showed how to <a href="https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/">vibe-code an MCP server using Micronaut</a>. You know I usually talk about <a href="https://micronaut.io/">Micronaut</a>, but this time, I wanted to experiment with Quarkus, and in particular with its built-in support for <a href="https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html">implementing MCP servers</a>.</p>
<h2 id="getting-started-with-quarkus-mcp-support">Getting started with Quarkus’ MCP support</h2>
<p>I created a brand new Quarkus project from IntelliJ IDEA, with its Quarkus template, and I added a couple key dependencies for JSON marshalling, but even more important, for the MCP support:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.quarkus<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>quarkus-resteasy-jackson<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.quarkiverse.mcp<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>quarkus-mcp-server-sse<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.2.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>As I’m going to deploy the server in the cloud, I chose to go with an SSE server: <a href="https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse">Server Sent Events</a>. The STDIO protocol is usually used for MCP servers running locally along the MCP host (i.e. your application invoking the tool).</p>
<p>Instead of going with the usual <em>weather forecast</em> use case, which is a bit like the <em>hello world</em> of MCP servers, I decided to implement a service that calculates the phases of the moon! I got the idea from a recent post on Hackernews that pointed at a GitHub repository that offered <a href="https://github.com/oliverkwebb/moonphase/tree/main">different implementations of the calculation of the moon phases</a>. I used Gemini to convert the algorithm to Java, as there was no Java implementation.</p>
<p>I’ll spare you the details of the calculation, but you can have a look at the <a href="https://github.com/glaforge/moon-phases-quarkus-mcp-sse-server">code</a> I wrote (or Gemini wrote!) to do the math. However, I’ll show you the structure of my <code>MoonPhasesService</code> class:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">jakarta.inject.Singleton</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Singleton</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MoonPhasesService</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span><span style="color:#06287e">currentMoonPhase</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>moonPhaseAtUnixTimestamp(System.<span style="color:#4070a0">currentTimeMillis</span>()<span style="color:#bbb"> </span><span style="color:#666">/</span><span style="color:#bbb"> </span>1000L);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span><span style="color:#06287e">moonPhaseAtUnixTimestamp</span>(<span style="color:#902000">long</span><span style="color:#bbb"> </span>timeSeconds)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This service is able to give you the phase of the moon at this current moment in time, or you can specify a particular date, as a UNIX epoch time in seconds.</p>
<p>This service returns a <code>MoonPhase</code> object. It’s an <code>enum</code> that looks like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.fasterxml.jackson.annotation.JsonFormat</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@JsonFormat</span>(shape<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>JsonFormat.<span style="color:#4070a0">Shape</span>.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">enum</span><span style="color:#bbb"> </span>MoonPhase<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>NEW_MOON(<span style="color:#bbb">            </span><span style="color:#4070a0">&#34;🌑&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;new moon&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WAXING_CRESCENT(<span style="color:#bbb">     </span><span style="color:#4070a0">&#34;🌒&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;waxing crescent&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>FIRST_QUARTER(<span style="color:#bbb">       </span><span style="color:#4070a0">&#34;🌓&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;first quarter&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WAXING_GIBBOUS(<span style="color:#bbb">      </span><span style="color:#4070a0">&#34;🌔&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;waxing gibbous&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>FULL(<span style="color:#bbb">                </span><span style="color:#4070a0">&#34;🌕&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;full&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WANING_GIBBOUS(<span style="color:#bbb">      </span><span style="color:#4070a0">&#34;🌖&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;waning gibbous&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>LAST_QUARTER(<span style="color:#bbb">        </span><span style="color:#4070a0">&#34;🌗&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;last quarter&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WANING_CRESCENT(<span style="color:#bbb">     </span><span style="color:#4070a0">&#34;🌘&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;waning crescent&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>NEW_MOON_APPROACHING(<span style="color:#4070a0">&#34;🌑&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;new moon approaching&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// constructur</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// getter/setter for emoji and phase name...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>As I didn’t want to just return the moon phase name, I customized the serialization so that Jackson returns the <code>enum</code> values as normal objects. So, for example, the full moon will be returned as:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;phase&#34;</span>: <span style="color:#4070a0">&#34;full&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;emoji&#34;</span>: <span style="color:#4070a0">&#34;🌕&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now comes the interesting part! How to expose an MCP tool that LLMs can access? The <code>@Tool</code> and <code>@ToolArg</code> annotations are your friends!</p>
<p>Let’s implement a new class, in which I inject the <code>MoonPhasesService</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MoonPhasesMcpServer</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Inject</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>MoonPhasesService<span style="color:#bbb"> </span>moonPhasesService;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now, let’s create two tool methods: one that gives the current phase of the moon, and the other one that gives the phase at a given date.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Tool</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;current-moon-phase&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Provides the current moon phase&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>TextContent<span style="color:#bbb"> </span><span style="color:#06287e">currentMoonPhase</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextContent(moonPhasesService.<span style="color:#4070a0">currentMoonPhase</span>().<span style="color:#4070a0">toString</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The first one gives the current phase, as of the date of today. The name and description of the tool are very important, as they help LLMs figure out what this tool is doing, and understand when it should call this tool. I return a <code>TextContent</code> result. But it’s also possible other kinds of content pieces, like audio or image content, or a resource.</p>
<p>Let’s have a look at the other method, the one that gives the moon phase for a given date:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Tool</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;moon-phase-at-date&#34;</span>,<span style="color:#bbb"> </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Provides the moon phase at a certain date &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;(with a format of yyyy-MM-dd)&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>ToolResponse<span style="color:#bbb"> </span><span style="color:#06287e">moonPhaseAtDate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@ToolArg</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;localDate&#34;</span>,<span style="color:#bbb"> </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;The date for which the user wants to know the phase &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;of the moon (in yyyy-MM-dd format)&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>localDate)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>LocalDate<span style="color:#bbb"> </span>parsedLocalDate<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LocalDate.<span style="color:#4070a0">parse</span>(localDate);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>MoonPhase<span style="color:#bbb"> </span>moonPhase<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>moonPhasesService.<span style="color:#4070a0">moonPhaseAtUnixTimestamp</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>parsedLocalDate.<span style="color:#4070a0">toEpochDay</span>()<span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>86400);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>ToolResponse.<span style="color:#4070a0">success</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextContent(moonPhase.<span style="color:#4070a0">toString</span>()));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(DateTimeException<span style="color:#bbb"> </span>dte)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>ToolResponse.<span style="color:#4070a0">error</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;Not a valid date (yyyy-MM-dd): &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>localDate);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This time, the method also takes an argument. That’s why I annotated the parameter with a <code>@ToolArg</code> annotation, again with a name and description (including how the date should be formatted). Since this method can fail at the time of parsing the date string, I decided to return a <code>ToolResponse</code> which wraps either a result (the moon phase) or an error in case the parsing fails.</p>
<p>As you can see, it’s fairly easy to implement tools for an MCP server! You almost just need annotations, and that’s it!</p>
<p>This server isn’t secured in any way, to keep things simple in this article. But if you need to dig deeper and learn more about securing an MCP server, I invite you to read this article by Sergey Beryozkin on <a href="https://quarkus.io/blog/secure-mcp-sse-server/">getting ready for secure MCP with Qurkus MCP server</a>.</p>
<h2 id="running-the-server-and-checking-it-works">Running the server and checking it works</h2>
<p>To run this MCP server, you can simply run Quarkus in dev mode with the following command (if you’re using Maven):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>./mvnw quarkus:dev
</span></span></code></pre></div><p>You can quickly check that the endpoint is alive and running by simply going to your browser, and hitting this URL: http://localhost:8080/mcp/sse. You’ll see an Server Sent Event like this one:</p>
<pre tabindex="0"><code>event: endpoint
data: /mcp/messages/OTRiYzEyNTItNWY1Ni00NWJhLWExZTEtYzE5ZWU1YjdkNWQy
</code></pre><p>But we’re not really testing our two MCP tools.</p>
<p>An approach is to invoke the MCP server with LangChain4j, you can read more in this article about <a href="https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/">MCP client and server with the Java MCP SDK and LangChain4j</a> that I wrote earlier. So I won’t repeat myself today. And you can read more about <a href="https://docs.langchain4j.dev/tutorials/mcp">LangChain4j’s MCP support</a> in its documentation.</p>
<p>But here, I wanted to highlight a very convenient tool: the <a href="https://modelcontextprotocol.io/docs/tools/inspector">MCP inspector</a>. It’s a tool provided by the MCP project itself. It’s a Node-based tool that you can install and run locally on your machine, with the following <code>npx</code> command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>npx @modelcontextprotocol/inspector
</span></span></code></pre></div><p>It provides a UI to interact with an MCP server. Here, my MCP server is already deployed, I connected to it (I selected SSE, gave the URL of my server), requested the list of tools (shown in the middle pane), and invoked the tool that gives the phase of the moon at a given date (panel on the right of the screenshot):</p>
<p><figure>
  <a href="#img-551eaad4766cbd14f9e9ee64907673b7">
    <img src="/img/adk/mcp-inspector-moon-phases.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-551eaad4766cbd14f9e9ee64907673b7">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/mcp-inspector-moon-phases.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I really encourage you to use the MCP inspector to test your MCP servers manually. This is a very handy tool in your toolbelt.</p>
<h2 id="deploying-on-cloud-run">Deploying on Cloud Run</h2>
<p>So far so good, <em>it works on my machine</em>(™). What about deploying the server in the cloud, since we chose to go with an SSE MCP server? My go-to solution to host my apps quickly and efficiently is to containerize them and deploy them on <a href="https://cloud.google.com/run?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">Google Cloud Run</a>. Cloud Run is a managed platform to run containers that scale up upon traffic, and down to zero instances when there’s no activity (costing you 0 cent).</p>
<p><a href="https://cloud.google.com/blog/products/ai-machine-learning/ai-studio-to-cloud-run-and-cloud-run-mcp-server?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">Cloud Run made the highlight at Google I/O</a> this year, as it was announced that you can:</p>
<ul>
<li>Develop apps within <a href="https://aistudio.google.com/">AI Studio</a> and deploy them in one click on Cloud Run,</li>
<li>Deploy a <a href="https://blog.google/technology/developers/gemma-3/?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">Gemma 3</a> model on Cloud Run again with one click too from <a href="https://aistudio.google.com/prompts/new_chat?model=gemma-3-27b-it">AI Studio</a>,</li>
<li>And run <a href="https://github.com/GoogleCloudPlatform/cloud-run-mcp">Cloud Run’s own MCP server</a> to be able to deploy apps from your MCP powered IDEs and clients.</li>
</ul>
<p>Since Cloud Run is a container based platform, let’s containerize our application. Quarkus offers a handful of <code>Dockerfile</code>s depending on how you want to create your container. For some reason the native build ones didn’t work for me (I got a <em>“the &ndash;chmod option requires BuildKit”</em> error message, that I haven’t investigated further) so I went with the <code>Dockerfile.jvm</code> file, that I copied into <code>Dockerfile</code> at the root of my project, so that Cloud Build could easily pick it up and build it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>gcloud builds submit <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --tag gcr.io/YOUR_PROJECT_ID/moonphases
</span></span></code></pre></div><p>Once built, it’s available in Google Cloud <a href="https://cloud.google.com/artifact-registry/docs?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">Artifact Registry</a>. And I can deploy the containerized moon phases service to Cloud Run with the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>gcloud run deploy moonphases <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --allow-unauthenticated <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --image gcr.io/YOUR_PROJECT_ID/moonphases
</span></span></code></pre></div><p>Of course, you’ll have to update the <code>YOUR_PROJECT_ID</code> placeholders with the real Google Cloud project ID of your own project. And along the way, you’ll be requested to enable important APIs (artifact registry, cloud run, etc.)</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>If you’re interested, there’s a great page about <a href="https://cloud.google.com/run/docs/host-mcp-servers?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">hosting MCP servers on Cloud Run</a>, to learn more about the possibilities.</p>
      </div>
    </div><h2 id="bonus-configuring-the-mcp-server-in-agent-development-kit">Bonus: Configuring the MCP server in Agent Development Kit</h2>
<p>Of course, you can configure and <a href="https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/">invoke this MCP server from LangChain4j</a>, but let’s have a quick look at configuring and invoking it from ADK (Agent Development Kit):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>SseServerParameters<span style="color:#bbb"> </span>sseParams<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>SseServerParameters.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">url</span>(<span style="color:#4070a0">&#34;https://moonphases-2029713823481.europe-west1.run.app/mcp/sse&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>McpToolset.<span style="color:#4070a0">McpToolsAndToolsetResult</span><span style="color:#bbb"> </span>toolsAndToolsetResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>McpToolset.<span style="color:#4070a0">fromServer</span>(sseParams).<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>McpTool<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>moonPhasesTools<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>toolsAndToolsetResult.<span style="color:#4070a0">getTools</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>scienceTeacherAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You&#39;re a friendly science teacher
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        answering questions about scientific concepts.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        If the question is about about the phases of the moon,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you MUST call the `current-moon-phase` function tool
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to know the current phase as of right now,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        or the `moon-phase-at-date` function tool
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to know the phase of the moon on a particular day
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        (the date format is then yyyy-MM-dd).
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(moonPhasesTools)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>I’m not going to detail everything here, but if you want to learn more about ADK for Java, please read my <a href="https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/">getting started guide</a> that I published recently. What’s needed here is to configure the SSE server parameters, creating an MCP toolset, and then getting the list of tools, to pass to the agent via its <code>tools()</code> method.</p>
<p>For the record, here is what the ADK Dev UI shows when asking for the current phase of the moon, and the phase for a later date:</p>
<p><figure>
  <a href="#img-16401cca82a84721e4d25a9288e2eda1">
    <img src="/img/adk/mcp-moon-phases.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-16401cca82a84721e4d25a9288e2eda1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/mcp-moon-phases.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="summary">Summary</h2>
<p>In the article, we walked you through building an MCP server with Quarkus and deploying it on Google <a href="https://cloud.google.com/run?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">Cloud Run</a>.</p>
<p>First, we created a <a href="https://quarkus.io/">Quarkus</a> project with the necessary dependencies for the <a href="https://docs.quarkiverse.io/quarkus-langchain4j/dev/mcp.html">MCP support</a>. Then, we implemented a service to calculate moon phases and exposed it as MCP tools using <code>@Tool</code> and <code>@ToolArg</code> annotations provided by Quarkus. We used the <a href="https://modelcontextprotocol.io/docs/tools/inspector">MCP inspector</a> to test the server and we showed how to configure and invoke it from <a href="https://google.github.io/adk-docs/">ADK</a>, the Agent Development Kit. Finally, we containerized the application and deployed it to Google <a href="https://cloud.google.com/run?utm_campaign=CDR_0x7a40493f_user-journey_b423600838&amp;utm_medium=external&amp;utm_source=blog">Cloud Run</a> for scalability.</p>
<p>If you want to have a closer look at the full source code, you can <a href="https://github.com/glaforge/moon-phases-quarkus-mcp-sse-server">check out this repository</a> to learn more about creating your own MCP servers!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Expanding ADK Java LLM coverage with LangChain4j</title><link>https://glaforge.dev/posts/2025/06/05/expanding-adk-java-llm-coverage-with-langchain4j/</link><pubDate>Thu, 05 Jun 2025 16:41:26 +0200</pubDate><guid>https://glaforge.dev/posts/2025/06/05/expanding-adk-java-llm-coverage-with-langchain4j/</guid><description>&lt;p>Recently on these pages, I&amp;rsquo;ve covered &lt;a href="https://github.com/google/adk-java">ADK&lt;/a> (Agent Development Kit) for Java, launched at Google I/O 2025.
I showed how to get started &lt;a href="https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/">writing your first Java agent&lt;/a>,
and I shared a &lt;a href="https://glaforge.dev/posts/2025/05/27/adk-java-github-template/">Github template&lt;/a> that you can use to kick start your development.&lt;/p>
&lt;p>But you also know that I&amp;rsquo;m a big fan of, and a contributor to the &lt;a href="https://docs.langchain4j.dev/">LangChain4j project&lt;/a>,
where I&amp;rsquo;ve worked on the Gemini support, embedding models, GCS document loaders, Imagen generation, etc.&lt;/p></description><content:encoded>
<![CDATA[<p>Recently on these pages, I&rsquo;ve covered <a href="https://github.com/google/adk-java">ADK</a> (Agent Development Kit) for Java, launched at Google I/O 2025.
I showed how to get started <a href="https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/">writing your first Java agent</a>,
and I shared a <a href="https://glaforge.dev/posts/2025/05/27/adk-java-github-template/">Github template</a> that you can use to kick start your development.</p>
<p>But you also know that I&rsquo;m a big fan of, and a contributor to the <a href="https://docs.langchain4j.dev/">LangChain4j project</a>,
where I&rsquo;ve worked on the Gemini support, embedding models, GCS document loaders, Imagen generation, etc.</p>
<p>How can I reconcile both?
By <strong>integrating ADK and LangChain4j together</strong>!
But why?
Because currently, ADK for Java only supports two models: Gemini and Claude,
compared to the Python version that supports other models via its <a href="https://www.litellm.ai/">LiteLLM</a> integration.
So if I could integrate ADK with LangChain4j, I could make ADK Java access any model that LangChain4j supports!
Then developers could use models from OpenAI, Anthropic, Mistral, and also all the models that can run via Ollama,
like Gemma, Qwen, Phi, and others!</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>This is a work-in-progress glimpse into the ADK / LangChain4j integration
I&rsquo;ve been working on with Dmytro (LangChain4j&rsquo;s founder).
It&rsquo;s not yet been integrated in either ADK or LangChain4j.
Currently, it lives as a <a href="https://github.com/google/adk-java/pull/102">Pull Request</a> against the ADK Github project.
Stay tuned! I&rsquo;ll blog back when it&rsquo;s available!</p>
      </div>
    </div><h2 id="using-local-ollama-models-in-adk">Using local Ollama models in ADK</h2>
<p>Let&rsquo;s say you want to build a Java agent with ADK, using the <a href="https://qwenlm.github.io/blog/qwen3/">Qwen 3</a> model,
that you installed locally via Ollama. You have Ollama running on your computer and serving the model.
Then all you have to do is to configure the <a href="https://docs.langchain4j.dev/integrations/language-models/ollama">Ollama LangChain4j</a> model,
and wrap it in a <code>LangChain4j</code> ADK model adapter:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>OllamaChatModel<span style="color:#bbb"> </span>ollamaChatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>OllamaChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;qwen3:1.7b&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">baseUrl</span>(<span style="color:#4070a0">&#34;http://127.0.0.1:11434&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>scienceTeacherAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LangChain4j(ollamaChatModel))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful science teacher
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        who explains science concepts to kids and teenagers.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the following screenshot of the ADK Dev UI, I configured Ollama to serve a <a href="https://blog.google/technology/developers/gemma-3/">Gemma 3</a> model,
as you can see at the bottom left hand corner, where it shows the events, and LLM requests &amp; responses:</p>
<p><figure>
  <a href="#img-c410473970524fae29802f6dcfb1523b">
    <img src="/img/adk/adk-lc4j-ollama-gemma.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c410473970524fae29802f6dcfb1523b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/adk-lc4j-ollama-gemma.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="using-big-provider-models-in-adk">Using big provider models in ADK</h2>
<p>But you can use the <em>big guns</em> as well, including Anthropic models:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>AnthropicChatModel<span style="color:#bbb"> </span>claudeModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AnthropicChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;ANTHROPIC_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(CLAUDE_3_7_SONNET_20250219)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LangChain4j(claudeModel,<span style="color:#bbb"> </span>CLAUDE_3_7_SONNET_20250219))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful science teacher
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        who explains science concepts to kids and teenagers.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Or OpenAI, this time using a <strong>streaming model</strong>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>StreamingChatModel<span style="color:#bbb"> </span>openaiStreamingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>OpenAiStreamingChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;OPENAI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gpt-4o-mini&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LangChain4j(openaiStreamingModel))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful science teacher
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        who explains science concepts to kids and teenagers.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the ADK Dev UI, you can flip the switch to enable or disable streaming.
In that case, if you want to support both modes in the UI, <strong>configure two LangChain4j models:
the streaming and the non-streaming one</strong>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LangChain4j(openaiModel,<span style="color:#bbb"> </span>openaiStreamingModel))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful science teacher
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        who explains science concepts to kids and teenagers.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="what-about-tools">What about tools?</h2>
<p>With ADK, your agents can make use of tools, so if the underlying LangChain4j model supports function calling, tools will work too.
And there&rsquo;s one particular tool that I&rsquo;d like to mention: agent tools.
An agent can be a tool.
So you can mix and match different LLMs as sub-agents, or use a tool backed by a LangChain4j LLM.</p>
<p>For example, here&rsquo;s a main agent using Claude, and a tool agent using OpenAI to give weather information:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>StreamingChatModel<span style="color:#bbb"> </span>openaiStreamingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>OpenAiStreamingChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;OPENAI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gpt-4o-mini&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>weatherAgent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;weather-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Weather agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LangChain4j(openaiStreamingModel))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to always answer that the weather is sunny and 20°C.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>AnthropicChatModel<span style="color:#bbb"> </span>claudeModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AnthropicChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;ANTHROPIC_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(CLAUDE_3_7_SONNET_20250219)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent<span style="color:#bbb"> </span>agent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;friendly-weather-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Friend agent that knows about the weather&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LangChain4j(claudeModel,<span style="color:#bbb"> </span>CLAUDE_3_7_SONNET_20250219))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a friendly assistant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        If asked about the weather forecast for a city,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you MUST call the `weather-agent` function.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(AgentTool.<span style="color:#4070a0">create</span>(weatherAgent))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Mixing different models in a multi-agent scenario is quite interesting, as you can use the best model for the job.
Maybe you&rsquo;ll need to use a super fast model to do a simple classification task to route requests depending on the ask,
while you&rsquo;ll use a beefier model for the main task that requires more advanced thinking (like a Gemini 2.5 thinking model).</p>
<h2 id="summary">Summary</h2>
<p>This is still early days, as I mentioned in the beginning, it is just a <em>work-in-progress</em> right now,
but I believe it is a great way to <strong>extend ADK Java to supports a lot more models, including local ones</strong>,
and it opens up some <strong>interesting perspectives in multi-agent scenarios when mixing models together</strong>.</p>
<p>Stay tuned, I&rsquo;ll keep you posted on this development!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>An ADK Java GitHub template for your first Java AI agent</title><link>https://glaforge.dev/posts/2025/05/27/adk-java-github-template/</link><pubDate>Tue, 27 May 2025 13:01:52 +0200</pubDate><guid>https://glaforge.dev/posts/2025/05/27/adk-java-github-template/</guid><description>&lt;p>With the unveiling of the &lt;a href="https://github.com/google/adk-java/">Java version&lt;/a> of &lt;a href="https://google.github.io/adk-docs/">Agent Development Kit&lt;/a>
(ADK) which lets you &lt;strong>build AI agents in Java&lt;/strong>, I recently covered how to
&lt;a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">get started developing your first agent&lt;/a>.&lt;/p>
&lt;p>The installation and quickstart &lt;a href="https://google.github.io/adk-docs/get-started/">documentation&lt;/a> also helps for the first steps,
but I realized that it would be handy to provide a &lt;strong>template project&lt;/strong>, to further accelarate your &lt;em>time-to-first-conversation&lt;/em> with your Java agents!
This led me to play with GitHub&amp;rsquo;s &lt;em>template project&lt;/em> feature, which allows you to create a copy of the template project on your own account or organization.
It comes with a ready-made project structure, a configured &lt;code>pom.xml&lt;/code> file, and a first Java agent you can customize at will, and run from both the command-line or the ADK Dev UI.&lt;/p></description><content:encoded>
<![CDATA[<p>With the unveiling of the <a href="https://github.com/google/adk-java/">Java version</a> of <a href="https://google.github.io/adk-docs/">Agent Development Kit</a>
(ADK) which lets you <strong>build AI agents in Java</strong>, I recently covered how to
<a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">get started developing your first agent</a>.</p>
<p>The installation and quickstart <a href="https://google.github.io/adk-docs/get-started/">documentation</a> also helps for the first steps,
but I realized that it would be handy to provide a <strong>template project</strong>, to further accelarate your <em>time-to-first-conversation</em> with your Java agents!
This led me to play with GitHub&rsquo;s <em>template project</em> feature, which allows you to create a copy of the template project on your own account or organization.
It comes with a ready-made project structure, a configured <code>pom.xml</code> file, and a first Java agent you can customize at will, and run from both the command-line or the ADK Dev UI.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition tip">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2c0 0 0 0 0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c0 0 0 0 0 0c19.8 27.1 39.7 54.4 49.2 86.2l160 0zM192 512c44.2 0 80-35.8 80-80l0-16-160 0 0 16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
        <span>Clone the project</span>
      </div>
      <div class="admonition-content">
        <p>&#x27a1;&#xfe0f; <strong>Clone the <a href="https://github.com/glaforge/adk-java-maven-template"><code>adk-java-maven-template</code></a> project!</strong> &#x2b05;&#xfe0f;</p>
      </div>
    </div><p>The project follows a standard Java project structure:</p>
<pre tabindex="0"><code>project_folder/
├── pom.xml
└── src/
    └── main/
        └── java/
            └── com/
                └── example/
                    └── agent/
                        └── HelloWeatherAgent.java
</code></pre><p>The <code>pom.xml</code> build file declares the two ADK dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&lt;!-- The ADK core dependency --&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.adk<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-adk<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.1.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&lt;!-- The ADK dev web UI to debug your agent --&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.adk<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-adk-dev<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.1.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>And the <code>HelloWeatherAgent.java</code> class shows how to create a simple agent:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;hello-weather-agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Hello World&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a friendly assistant,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        answering questions in a concise manner.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When asked about weather information,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you MUST use the `getWeather` function.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(FunctionTool.<span style="color:#4070a0">create</span>(HelloWeatherAgent.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;getWeather&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>An agent that makes use of a tool to request weather forecasts:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Schema</span>(description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Get the weather forecast for a given city&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">getWeather</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Schema</span>(name<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;city&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>description<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Name of the city to get the weather forecast for&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>city)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;city&#34;</span>,<span style="color:#bbb"> </span>city,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;forecast&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Sunny day, clear blue sky, temperature up to 24°C&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>There are two ways to run the agent, via the command-line:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>mvn compile exec:java <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -Dexec.mainClass<span style="color:#666">=</span><span style="color:#4070a0">&#34;com.example.agent.HelloWeatherAgent&#34;</span>
</span></span></code></pre></div><p>Or the ADK Dev UI:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>mvn compile exec:java <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -Dexec.mainClass<span style="color:#666">=</span><span style="color:#4070a0">&#34;com.google.adk.web.AdkWebServer&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -Dexec.classpathScope<span style="color:#666">=</span><span style="color:#4070a0">&#34;compile&#34;</span>
</span></span></code></pre></div><p>Which will show the nice and handy development UI, to help you prototype and debug your agent:</p>
<p><figure>
  <a href="#img-267fd9b21af432cf80284c9d17e26cd3">
    <img src="https://github.com/glaforge/adk-java-maven-template/raw/main/adk-dev-ui.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-267fd9b21af432cf80284c9d17e26cd3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://github.com/glaforge/adk-java-maven-template/raw/main/adk-dev-ui.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>&#x1f603; <strong>Happy Java AI agent building!</strong> &#x1f6e0;&#xfe0f;</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Things you never dared to ask about LLMs — Take 2</title><link>https://glaforge.dev/talks/2025/05/26/things-you-never-dared-to-ask-about-llms-take-2/</link><pubDate>Mon, 26 May 2025 12:26:41 +0200</pubDate><guid>https://glaforge.dev/talks/2025/05/26/things-you-never-dared-to-ask-about-llms-take-2/</guid><description>&lt;p>Recently, I had the chance to deliver this talk on the mysteries of LLMs, at &lt;a href="https://www.devoxx.fr/agenda-2025/talk/sous-le-capot-des-llms-toutes-ces-questions-que-vous-n-avez-jamais-ose-poser/">Devoxx France&lt;/a>, with my good friend &lt;a href="https://www.linkedin.com/in/DidierGirard/">Didier Girard&lt;/a>,
It was fun to uncover the oddities of LLMs, and better understand where they thrive or fail, and why.
I also delivered this talk alone at &lt;a href="https://devoxx.pl/talk-details/?id=2171">Devoxx Poland&lt;/a>.&lt;/p>
&lt;p>In this post, I&amp;rsquo;d like to share an update of the presentation deck, with a few additional slides here and there, to cover for example&lt;/p></description><content:encoded>
<![CDATA[<p>Recently, I had the chance to deliver this talk on the mysteries of LLMs, at <a href="https://www.devoxx.fr/agenda-2025/talk/sous-le-capot-des-llms-toutes-ces-questions-que-vous-n-avez-jamais-ose-poser/">Devoxx France</a>, with my good friend <a href="https://www.linkedin.com/in/DidierGirard/">Didier Girard</a>,
It was fun to uncover the oddities of LLMs, and better understand where they thrive or fail, and why.
I also delivered this talk alone at <a href="https://devoxx.pl/talk-details/?id=2171">Devoxx Poland</a>.</p>
<p>In this post, I&rsquo;d like to share an update of the presentation deck, with a few additional slides here and there, to cover for example</p>
<ul>
<li>the difficulty of LLMs to work with acronyms, scientific molecule names, plant names, special uncommon vocabulary, which require more tokens and weakens <em>attention</em>,</li>
<li>the difference between deterministic and probabilistic problems, and why predictive models are still important,</li>
<li>some limits of LLMs with regards to understanding dates, data ownership, or the fact they can&rsquo;t easily forget what they learned.</li>
</ul>
<script async class="speakerdeck-embed" data-id="1d3eae3a34d846888f7183bed5f0597e" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>This was fun delivering the talk with Didier, as a friendly dialogue makes things more entertaining!
We were lucky that this talk was recorded (however, in French &#x1f1eb;&#x1f1f7;) and you can watch the <a href="https://www.youtube.com/watch?v=C6tZE5OgqUc">video</a> below:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/C6tZE5OgqUc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Fortunately, this talk was also recorded at Devoxx Poland, in English this time:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/M2FRKPLtG_g?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Beyond the chatbot or AI sparkle: a seamless AI integration</title><link>https://glaforge.dev/posts/2025/05/23/beyond-the-chatbot-or-ai-sparkle-a-seamless-ai-integration/</link><pubDate>Fri, 23 May 2025 14:27:27 +0200</pubDate><guid>https://glaforge.dev/posts/2025/05/23/beyond-the-chatbot-or-ai-sparkle-a-seamless-ai-integration/</guid><description>&lt;p>When I talk about &lt;a href="http://localhost:1313/tags/generative-ai">Generative AI&lt;/a>, whether it&amp;rsquo;s with developers at conferences or with customers, I often find myself saying the same thing: &lt;strong>chatbots are just one way to use Large Language Models&lt;/strong> (LLMs).&lt;/p>
&lt;p>Unfortunately, I see many articles or presentations that just focus on demonstrating LLMs at work within the context of chatbots. I feel guilty of showing the traditional chat interfaces too. But there&amp;rsquo;s so much more to it!&lt;/p></description><content:encoded>
<![CDATA[<p>When I talk about <a href="http://localhost:1313/tags/generative-ai">Generative AI</a>, whether it&rsquo;s with developers at conferences or with customers, I often find myself saying the same thing: <strong>chatbots are just one way to use Large Language Models</strong> (LLMs).</p>
<p>Unfortunately, I see many articles or presentations that just focus on demonstrating LLMs at work within the context of chatbots. I feel guilty of showing the traditional chat interfaces too. But there&rsquo;s so much more to it!</p>
<p>For example, when I <a href="https://glaforge.dev/posts/2025/01/06/analyzing-trends-and-topics-from-blueskys-firehose-with-generative-ai/">analyzed Bluesky topic trends</a>, there was no chatbot involved, but <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstarts/quickstart-multimodal?utm_campaign=CDR_0x7a40493f_default_b419777287&amp;utm_medium=external&amp;utm_source=blog">Gemini</a> and <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings?utm_campaign=CDR_0x7a40493f_default_b419777287&amp;utm_medium=external&amp;utm_source=blog">embedding models</a> helped me make sense of clusters of posts. Or when I played with <a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">generating short science fiction stories</a>, there was again no chat interface, but the LLM and the <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/imagen/3-0-generate-002?utm_campaign=CDR_0x7a40493f_default_b419777287&amp;utm_medium=external&amp;utm_source=blog">Imagen</a> image generation models were used for their creative facet.</p>
<p>LLMs are also very powerful to replicate more classical Natural Language Processing tasks (NLP) like sentiment analysis, entity extraction, etc. But sometimes dedicated predictive models are more (cost) effective at those tasks. However, <strong>LLMs allow developers to implement those NLP use cases easily</strong> by properly prompting their favorite model. And developers may then be able to add nice and seamless features here and there in their applications.</p>
<h2 id="the-trigger">The trigger</h2>
<p>What led me to share my views on these usage patterns of AI? It’s when I read Kojo Osei’s article titled “<a href="https://kojo.blog/ai-button/">there should be no AI button</a>”. Indeed, we’re seeing a <strong>proliferation of AI “<em>sparkle”</em> buttons</strong> in various applications and websites. This feels like a quick hack, an extra patch, to say that the application is smart, but it also adds unnecessary cognitive load and breaks the user&rsquo;s focus. <strong>It takes more than an AI sparkle button to make an application intuitive and seamlessly smart</strong>!</p>
<p>The author argues that dedicated &ldquo;AI buttons&rdquo; in user interfaces are a flawed and temporary design choice. The author says, and I agree, that <strong>the best AI user experience is seamless and integrated</strong>, and that AI buttons create unnecessary limitations and frustrations. The article calls for more contextual alternatives that don&rsquo;t artificially segregate AI functionalities, and avoid breaking the flow of the user.</p>
<h2 id="my-take">My take</h2>
<p>In my opinion, the best way to use AI is to build it right into your applications, making them smarter and more helpful <strong>in a way that feels completely natural to the user</strong>. It&rsquo;s not about hiding the fact that AI is involved – on the contrary, I believe users should know. But, as people go about their daily tasks in an app or on a website, AI should be there to assist them smoothly, <strong>without them needing to click a special &ldquo;AI button&rdquo;</strong> to make something happen, or <strong>having to open a chat window to ask for help</strong>.</p>
<p>I see chat interfaces as one specific use case for LLMs, and that&rsquo;s fine. But they aren&rsquo;t the only option, and I don&rsquo;t think they&rsquo;re always the most intuitive or the least disruptive. <strong>People need to stay in their flow</strong>, focused on what they&rsquo;re doing. Their <strong>work should be augmented by AI</strong>, not broken up by extra clicks or messages.</p>
<p>Think about it: if someone is deep in concentration, writing or designing, the last thing they need is to stop, look for a button, and then start a conversation with an AI to get a suggestion. That kind of interruption significantly increases their cognitive load, breaks their focus and makes the whole process feel clunky.</p>
<h2 id="examples-of-more-seamless-and-intuitive-flows">Examples of more seamless and intuitive flows</h2>
<p>Personally, I like when a smart application is proactive but doesn’t get in the way of my normal flow. To illustrate this, let’s think about some common patterns I’ve seen that I found successful at this:</p>
<ul>
<li>
<p>In Gmail and Chat, the UI shows me a <a href="https://support.google.com/chat/answer/12918975?hl=en&amp;co=GENIE.Platform%3DDesktop">summary of the ongoing conversation</a> that I missed. I can still go through the unread messages, but I can also be up-to-speed rapidly by reading the summary, and then quickly glancing through the messages to get more details.</p>
</li>
<li>
<p>Another summarization example is when I use <a href="https://obsidian.md/">Obsidian</a> to take notes of articles I find interesting and want to remember. I installed a Chrome add-on, the <a href="https://help.obsidian.md/web-clipper/interpreter">Obsidian web clipper and its “interpreter”</a>, that I configured to use Gemini, to create a bullet point summary of the articles, and create relevant tags that help me navigate through similar content I’ve already in my notes.</p>
</li>
<li>
<p>For coding, we (developers) are now used to the seamless LLM-powered code completion. I like how it waits a little before suggesting anything. And often, when I use <a href="https://codeassist.google/">Gemini Code Assist</a>, I have the impression that the LLM read my mind and knew exactly about the code I wanted to write. And if it’s not what I wanted, I’m not really disrupted or distracted, as I can continue typing if the suggestion doesn’t make sense.</p>
</li>
<li>
<p>To stay in the realm of developer workflows, your AI peer is at work as the first responder to the tickets users create, then you can hop in the conversation as the user details their issues. Similarly, for PRs (Pull Requests), your AI coding bot can analyze the code you submitted and make first recommendations to improve it, like the <a href="https://github.com/marketplace/gemini-code-assist">Gemini Code Assist bot does on Github</a>.</p>
</li>
<li>
<p>Before creating a ticket, the bug tracker could also take advantage of LLM-powered or embedding-based semantic search to find similar issues, to avoid creating duplicates, or guide the reporter to pick up the right component or category, depending on what the issue is all about.</p>
</li>
<li>
<p>Large Language Models are great at creating first drafts of documents. Give it the right outline, directions, ideas, and you’ll get a first sketch that you can refine — even a few sentences in this very article that you read were first drafted with Gemini!</p>
</li>
<li>
<p>You can also imagine this draft generation in contexts like CRM apps, where users can create a first draft of something they want to send their customers, depending on the current situation of that customer. Or like in the linked article, with this image creation app, where the user draws a few broad strokes of colors to guide the image generation to follow along.</p>
</li>
<li>
<p>But of course if you’re already working in a chat environment (like Slack or similar messaging platforms), it still makes sense to be able to chat with an AI-powered bot! I don’t want to get rid of chat spaces altogether. But an AI assistant should be that: an assistant, a peer, a colleague you can involve where needed, or who can nudge you to tell you <em>“hey, you forgot to add a test”</em>, or <em>“here are the relevant PRDs or bug entries about this new feature you’re talking about”</em>, or <em>“here’s a summary of the relevant past conversations on that topic”</em>.</p>
</li>
</ul>
<p>These are some examples of non-intrusive and seamless integrations, but <strong>UX designers need to rethink the app</strong> or website, and be acquainted with what LLMs can offer. <strong>Making an application smart isn’t just adding an AI sparkle button or a chatbot interface</strong>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>While chatbots are great for things like customer support or answering direct questions, they&rsquo;re not always the best fit for every situation. For many tasks, I believe AI assistance should be <strong>more like a quiet, helpful partner</strong> — there when you need it, maybe even anticipating what you need, without you having to constantly ask.</p>
<p>What I really believe we should aim for is AI that boosts what people are already doing, making their work easier and more powerful without adding extra steps. We should be <strong>creating experiences where AI enhances the tools people use every day</strong>, making them more efficient and insightful, <strong>without forcing them to constantly switch contexts</strong> or explicitly request AI intervention for every little thing via a sparkle button or a chat message.</p>
<p>Ultimately, I think the AI integrations that will truly succeed are the ones that don&rsquo;t feel like an add-on. Instead, they&rsquo;ll feel like a natural, intelligent part of the system. My ideal is to see us build AI experiences that genuinely empower people by <strong>working seamlessly in the background</strong>, helping them <strong>stay focused</strong> and achieve more, more rapidly.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Write AI agents in Java — Agent Development Kit getting started guide</title><link>https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/</link><pubDate>Tue, 20 May 2025 11:48:50 +0200</pubDate><guid>https://glaforge.dev/posts/2025/05/20/writing-java-ai-agents-with-adk-for-java-getting-started/</guid><description>&lt;p>At Google Cloud Next ‘25, last April, Google released &lt;a href="https://google.github.io/adk-docs/">Agent Development Kit&lt;/a> (ADK) for Python, a &lt;strong>flexible and modular framework for developing and deploying AI agents&lt;/strong>.&lt;/p>
&lt;p>Now at Google I/O, a &lt;strong>Java version of ADK&lt;/strong> has been made available! And I’m glad to have had the chance to participate in its launch, via code samples, documentation, and helping shape the API so it’s idiomatic for Java developers.&lt;/p>
&lt;p>In this article, my goal is to give you the basis to get started with the ADK framework, in Java, using the Gemini model, and running your first Java agents locally.&lt;/p></description><content:encoded>
<![CDATA[<p>At Google Cloud Next ‘25, last April, Google released <a href="https://google.github.io/adk-docs/">Agent Development Kit</a> (ADK) for Python, a <strong>flexible and modular framework for developing and deploying AI agents</strong>.</p>
<p>Now at Google I/O, a <strong>Java version of ADK</strong> has been made available! And I’m glad to have had the chance to participate in its launch, via code samples, documentation, and helping shape the API so it’s idiomatic for Java developers.</p>
<p>In this article, my goal is to give you the basis to get started with the ADK framework, in Java, using the Gemini model, and running your first Java agents locally.</p>
<blockquote>
<p>Be sure to checkout this video I recorded on how to get started
and learn more about some key concepts of ADK:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/VM3b3csBeUc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And if you want to start from a ready-made project with a sample agent using tools,
checkout the article on my
<a href="https://glaforge.dev/posts/2025/05/27/adk-java-github-template/">GitHub template project</a>,
or a direct link to the
<a href="https://github.com/glaforge/adk-java-maven-template">template project</a>.</p>
<p>In the meantime, let&rsquo;s now focus on the key steps to write your first AI agents with ADK!</p></blockquote>
<h2 id="project-structure">Project structure</h2>
<p>Create a usual Java project structure like the following, with your Java sources in <code>src/main/java</code>:</p>
<pre tabindex="0"><code>[YOUR_PROJECT_DIRECTORY]/
 └—— pom.xml
 └—— src/
     └—— main/
         └—— java/
             └—— agents/
                 └—— ScienceTeacherAgent.java
</code></pre><p>Let’s have a look at a minimal <code>pom.xml</code> to compile and run your agent:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#007020">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;project</span> <span style="color:#4070a0">xmlns=</span><span style="color:#4070a0">&#34;http://maven.apache.org/POM/4.0.0&#34;</span>
</span></span><span style="display:flex;"><span>         <span style="color:#4070a0">xmlns:xsi=</span><span style="color:#4070a0">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span>
</span></span><span style="display:flex;"><span>         <span style="color:#4070a0">xsi:schemaLocation=</span><span style="color:#4070a0">&#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;modelVersion&gt;</span>4.0.0<span style="color:#062873;font-weight:bold">&lt;/modelVersion&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>adk-agents<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>adk-agents<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.0-SNAPSHOT<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependencies&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#60a0b0;font-style:italic">&lt;!-- ADK core dependency --&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.adk<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-adk<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.1.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#60a0b0;font-style:italic">&lt;!-- ADK dev web UI and API server --&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.adk<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-adk-dev<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.1.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependencies&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/project&gt;</span>
</span></span></code></pre></div><p>You just need a couple of dependencies:</p>
<ul>
<li>the Core ADK framework dependency, and</li>
<li>the ADK web server that provides a nice and useful Dev UI to interact with your agents, as well as an API server.</li>
</ul>
<h2 id="your-first-agent">Your first agent</h2>
<p>Now that the project is set up, let’s implement a very simple agent. It’ll be a single agent. And it won’t yet use tools to interact with the external world. We’ll explore multi agents and tool support later on, in more advanced articles.</p>
<p>So let’s have a look at a science teacher agent, whose role is to help kids and teenagers understand scientific concepts in a friendly and approachable way.</p>
<p>Here’s the full source code. You can expand this block to see it all, but we’ll explain everything further down, in a piecemeal fashion.</p>

<details>
  <summary>Click to see the full source code</summary>
  <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">agents</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.nio.charset.StandardCharsets.UTF_8</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.Scanner</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.agents.BaseAgent</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.agents.LlmAgent</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.events.Event</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.runner.InMemoryRunner</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.adk.sessions.Session</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Content</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.genai.types.Part</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.reactivex.rxjava3.core.Flowable</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">/** Science teacher agent. */</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">ScienceTeacherAgent</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>BaseAgent<span style="color:#bbb"> </span>ROOT_AGENT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>initAgent();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>BaseAgent<span style="color:#bbb"> </span><span style="color:#06287e">initAgent</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                You are a helpful science teacher that explains
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                science concepts to kids and teenagers.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>InMemoryRunner<span style="color:#bbb"> </span>runner<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemoryRunner(ROOT_AGENT);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Session<span style="color:#bbb"> </span>session<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">sessionService</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">createSession</span>(runner.<span style="color:#4070a0">appName</span>(),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;student&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">blockingGet</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Scanner<span style="color:#bbb"> </span>scanner<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Scanner(System.<span style="color:#4070a0">in</span>,<span style="color:#bbb"> </span>UTF_8))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">print</span>(<span style="color:#4070a0">&#34;\nYou &gt; &#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>String<span style="color:#bbb"> </span>userInput<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>scanner.<span style="color:#4070a0">nextLine</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#4070a0">&#34;quit&#34;</span>.<span style="color:#4070a0">equalsIgnoreCase</span>(userInput))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Content<span style="color:#bbb"> </span>userMsg<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(userInput));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Flowable<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>runner.<span style="color:#4070a0">runAsync</span>(session.<span style="color:#4070a0">userId</span>(),<span style="color:#bbb"> </span>session.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span>userMsg);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">print</span>(<span style="color:#4070a0">&#34;\nAgent &gt; &#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>events.<span style="color:#4070a0">blockingForEach</span>(event<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(event.<span style="color:#4070a0">stringifyContent</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div></details>

<p>So what does the simplest agent look like?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LlmAgent.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;science-app&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;Science teacher agent&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">instruction</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful science teacher that explains
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        science concepts to kids and teenagers.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>An agent consists of a name, a description, a model, and some instructions, and that’s it! In other articles, we’ll also see how to give it tools, how to use state, how it can interact with other agents and workflows, but for now, let’s stick with this simple science teacher agent.</p>
<h2 id="running-your-agent">Running your agent</h2>
<p>Let’s say you’ve saved the above agent in a <code>ROOT_AGENT</code> static field of your class (it’ll come in handy for the Dev UI later on). Now we need to instantiate a <em>runner</em> and a <em>session</em> to run the agent.</p>
<p>First, you instantiate an <code>InMemoryRunner</code> with your agent:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>InMemoryRunner<span style="color:#bbb"> </span>runner<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemoryRunner(ROOT_AGENT);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then you create a session with the <code>SessionService</code> that you can grab from the runner itself (passing an application name and user ID):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Session<span style="color:#bbb"> </span>session<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">sessionService</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">createSession</span>(runner.<span style="color:#4070a0">appName</span>(),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;student&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">blockingGet</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now, if you want to have a familiar chatbot-like experience, you’ll loop over and alternate between user provided messages, and LLM generated answers. Let’s use a <code>Scanner</code> to get the input from the user, inside a big <code>while</code> loop, and send the user’s prompt to the LLM agent, via runner’s <code>runAsync()</code> method, passing the session and the message. The agent will reply with a flow of events that we can then print:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(Scanner<span style="color:#bbb"> </span>scanner<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Scanner(System.<span style="color:#4070a0">in</span>,<span style="color:#bbb"> </span>UTF_8))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">print</span>(<span style="color:#4070a0">&#34;\nYou &gt; &#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>userInput<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>scanner.<span style="color:#4070a0">nextLine</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#4070a0">&#34;quit&#34;</span>.<span style="color:#4070a0">equalsIgnoreCase</span>(userInput))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">break</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Content<span style="color:#bbb"> </span>userMsg<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">fromParts</span>(Part.<span style="color:#4070a0">fromText</span>(userInput));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Flowable<span style="color:#666">&lt;</span>Event<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>events<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>runner<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">runAsync</span>(session.<span style="color:#4070a0">userId</span>(),<span style="color:#bbb"> </span>session.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span>userMsg);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">print</span>(<span style="color:#4070a0">&#34;\nAgent &gt; &#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>events.<span style="color:#4070a0">blockingForEach</span>(event<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(event.<span style="color:#4070a0">stringifyContent</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="define-environment-variables">Define environment variables</h2>
<p>You’ll need to export two environment variables:</p>
<ul>
<li>a Gemini key that you can <a href="https://ai.google.dev/gemini-api/docs/api-key">get from AI Studio</a>,</li>
<li>a variable to specify you’re not using Vertex AI this time.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">GOOGLE_GENAI_USE_VERTEXAI</span><span style="color:#666">=</span>FALSE
</span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">GOOGLE_API_KEY</span><span style="color:#666">=</span>AIzaSyDF...
</span></span></code></pre></div><p>In this article, let’s use the Google AI / DeepMind endpoint and API key for Gemini, but you can also use Gemini from Google Cloud Vertex AI.</p>
<h2 id="executing-the-agent-from-the-command-line">Executing the agent from the command line</h2>
<p>From the command line, let’s use Maven’s exec java plugin to launch this science teacher agent:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>mvn compile exec:java -Dexec.mainClass<span style="color:#666">=</span><span style="color:#4070a0">&#34;agents.ScienceTeacherAgent&#34;</span>
</span></span></code></pre></div><p>Let’s see the output when asking a simple question about <em>“qubits”</em>!</p>
<pre tabindex="0"><code>[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------&lt; adk-agents:adk-agents &gt;------------------------
[INFO] Building adk-agents 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ adk-agents ---
[INFO] skip non existing resourceDirectory /Users/me/Projects/adk-agents/src/main/resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ adk-agents ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- exec:3.5.0:java (default-cli) @ adk-agents ---

You &gt; What is a qbit? Please answer in a concise manner.

Agent &gt; Hey there, future tech wiz! 👋

A **qubit** (short for &#34;quantum bit&#34;) is the basic unit of information
in a quantum computer. Unlike a regular bit in your computer, which is
either a 0 or a 1, a qubit can be a 0, a 1, or *both at the same time*
thanks to something called **superposition**! This &#34;both at once&#34;
ability is what makes quantum computers super powerful for certain
kinds of problems.

You &gt; quit
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0:22 min
[INFO] Finished at: 2025-05-19T01:00:12+02:00
[INFO] ------------------------------------------------------------------------
</code></pre><p>And the conversation can go on and on, till you’re bored and you enter <em>“quit”</em> to end the chat.</p>
<h2 id="executing-the-agent-in-the-dev-ui">Executing the agent in the Dev UI</h2>
<p>ADK comes with a Dev web UI (and API server) that you can use to run your agents, see how they behave, the data they exchange, how they interact with the underlying LLM. So let’s talk about this.</p>
<p>The following Maven command will launch the Dev UI:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>mvn exec:java <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -Dexec.mainClass<span style="color:#666">=</span><span style="color:#4070a0">&#34;com.google.adk.web.AdkWebServer&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -Dexec.classpathScope<span style="color:#666">=</span><span style="color:#4070a0">&#34;compile&#34;</span>
</span></span></code></pre></div><p><strong>Note</strong>: You can add a different path or even use a sub-directory (if you want to expose only agents in a certain package) by adding and customising this flag: <code>-Dexec.args=&quot;--adk.agents.source-dir=src/main/java/com/foo/agents&quot;</code>.</p>
<p>This time, you run the <code>AdkWebServer</code> class from the second dependency (<code>google-adk-dev</code>). There’s one constraint currently for the Dev UI to grab and run your agents: they have to be statically initialized and stored in a <code>public static BaseAgent ROOT_AGENT</code> field. That’s why the class had the following structure:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">ScienceTeacherAgent</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>BaseAgent<span style="color:#bbb"> </span>ROOT_AGENT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>initAgent();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>BaseAgent<span style="color:#bbb"> </span><span style="color:#06287e">initAgent</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>...<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>To access the Dev UI, just head over to your browser and open the following URL: http://localhost:8080/dev-ui</p>
<p>You should see something similar to this:</p>
<p><figure>
  <a href="#img-b39a6121356dd845d6c6f174fe8b8fe4">
    <img src="/img/adk/adk-dev-ui-science-teacher.png"
      alt="adk-dev-ui-science-teacher.png"
       />
  </a>
  <figcaption>adk-dev-ui-science-teacher.png</figcaption>
</figure>
<div class="lightbox" id="img-b39a6121356dd845d6c6f174fe8b8fe4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/adk/adk-dev-ui-science-teacher.png"
    alt="adk-dev-ui-science-teacher.png"
     />
  <div class="lightbox-caption">adk-dev-ui-science-teacher.png</div>
</div>
</p>
<ul>
<li>You can <strong>select an agent</strong> from the loaded agents in the top left hand corner.</li>
<li>When clicking on the <strong>events</strong>, you can see the popup over the left pane that shows all the details of the events that are flowing through, as well as the <strong>input request</strong> and <strong>output response</strong> from the LLM.</li>
<li>On the right side, in the chat area, you can see the <strong>dialogue</strong> between the user and the AI, as well as potential <strong>tool calls</strong>.</li>
<li>You can also <strong>enable token streaming</strong> at the top of the screen with the slide if you want to see responses being streamed as they are generated by the model.</li>
</ul>
<h2 id="congratulations">Congratulations!</h2>
<p>Wait, what? Are we already done?
Yes, you’ve coded and executed your first Java agent with the ADK framework!</p>
<h2 id="now-what">Now what?</h2>
<p>Many possible next steps, and also pointers, that I’d like to share with you.</p>
<ul>
<li>First of all, read the <a href="https://developers.googleblog.com/en/agents-adk-agent-engine-a2a-enhancements-google-io/?utm_campaign=CDR_0x7a40493f_default_b418955413&amp;utm_medium=external&amp;utm_source=blog">article announcing the launch of Java ADK</a>, and other news related to <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview?utm_campaign=CDR_0x7a40493f_default_b418955413&amp;utm_medium=external&amp;utm_source=blog">Agent Engine</a>, and <a href="https://github.com/google/A2A">A2A</a> (Agent to Agent Protocol).</li>
<li>Think about how you can integrate such an agent inside an existing Java application that you want to enhance with advanced agentic capabilities.</li>
<li>You may want to <a href="https://google.github.io/adk-docs/deploy/cloud-run/">deploy an agent on Cloud Run</a>, for example, or host it anywhere else you’d like, as it’s just an open source framework not tied to <a href="https://cloud.google.com/?utm_campaign=CDR_0x7a40493f_default_b418955413&amp;utm_medium=external&amp;utm_source=blog">Google Cloud</a>.</li>
<li>Start <a href="https://google.github.io/adk-docs/">learning more</a> about the concepts behind the framework, and what it’s capable of.</li>
<li>Check out the <a href="https://github.com/google/adk-java">source code on Github</a>.</li>
<li>I’d like to invite you to look at more substantial <a href="https://github.com/google/adk-docs/tree/main/examples/java/snippets/src/main/java">examples</a> and <a href="https://github.com/google/adk-samples/tree/main/java">samples</a> than my science teacher.</li>
<li>My colleague Abi implemented a <a href="https://medium.com/google-cloud/build-powerful-stateful-ai-agents-in-java-with-agent-development-kit-adk-0f7e2cd3d094">patent research and analysis agent</a> in Java, with ADK, deployed on Cloud Run, with data on AlloyDB.</li>
</ul>
<p>I’m really looking forward to hearing from you about what you’ll be building with ADK for Java! You can expect many more articles on this blog on the topic of ADK. So stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Vibe coding an MCP server with Micronaut, LangChain4j, and Gemini</title><link>https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/</link><pubDate>Fri, 02 May 2025 19:35:05 +0200</pubDate><guid>https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/</guid><description>&lt;p>Unlike Quarkus and Spring Boot, Micronaut doesn&amp;rsquo;t (yet?) provide a module to facilitate the implementation of &lt;a href="https://modelcontextprotocol.io/">MCP&lt;/a> servers (Model Context Protocol).
But being my favorite framework, I decided to see what it takes to build a quick implementation, by &lt;em>vibe coding&lt;/em> it, with the help of Gemini!&lt;/p>
&lt;p>In a recent article, I explored &lt;a href="https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/">how to use the MCP reference implementation for Java to implement an MCP server&lt;/a>,
served as a servlet via Jetty, and to call that server from &lt;a href="https://docs.langchain4j.dev/tutorials/mcp/">LangChain4j&amp;rsquo;s great MCP support&lt;/a>.
One approach with Micronaut may have been to somehow integrate the servlet I had built via Micronaut&amp;rsquo;s servlet support, but that didn&amp;rsquo;t really feel like a genuine and native way to implement a server, so I decided to do it from scratch.&lt;/p></description><content:encoded>
<![CDATA[<p>Unlike Quarkus and Spring Boot, Micronaut doesn&rsquo;t (yet?) provide a module to facilitate the implementation of <a href="https://modelcontextprotocol.io/">MCP</a> servers (Model Context Protocol).
But being my favorite framework, I decided to see what it takes to build a quick implementation, by <em>vibe coding</em> it, with the help of Gemini!</p>
<p>In a recent article, I explored <a href="https://glaforge.dev/posts/2025/05/02/vibe-coding-an-mcp-server-with-micronaut-and-gemini/">how to use the MCP reference implementation for Java to implement an MCP server</a>,
served as a servlet via Jetty, and to call that server from <a href="https://docs.langchain4j.dev/tutorials/mcp/">LangChain4j&rsquo;s great MCP support</a>.
One approach with Micronaut may have been to somehow integrate the servlet I had built via Micronaut&rsquo;s servlet support, but that didn&rsquo;t really feel like a genuine and native way to implement a server, so I decided to do it from scratch.</p>
<h2 id="vibe-coding-with-gemini">Vibe coding with Gemini</h2>
<p>The concept of <em>vibe coding</em> came from a <a href="https://x.com/karpathy/status/1886192184808149383">tweet from Andrej Karpathy</a>
who defined the concept as interacting with an LLM to build a new prototype or weekend project, and iterating with the LLM till it works, but without looking at or touching the code yourself.
It&rsquo;s quite a bit different than using AI assistance to build a production-ready code base.
And Simon Willison&rsquo;s just written a good piece on <a href="https://simonwillison.net/2025/May/1/not-vibe-coding/">what is and what is not <em>vibe coding</em></a>.</p>
<p>I started throwing <strong>Gemini 2.5 Pro</strong> some simple prompts for creating an MCP server with Micronaut, with Java 21, but it would not generate something really usable, at least not in one-shot!
For example, it would not use Server-Sent Events, or it hadn&rsquo;t figured out how that the protocol is using JSON-RPC, etc.
So instead of steering the LLM in the right direction via multiple prompts, I reused my tacticts of feeding as much information as needed into the prompt.
This is the approach I took to <a href="https://glaforge.dev/posts/2025/03/03/llms-txt-to-help-llms-grok-your-content/">grok one&rsquo;s own content with LLMs.txt</a>, or when I wrote about
<a href="https://glaforge.dev/posts/2025/02/15/the-power-of-large-context-windows-for-your-documentation-efforts/">the power of large context windows for your documentation efforts</a>.</p>
<p>So what was the successful prompt?</p>
<blockquote>
<p>Let&rsquo;s implement a Model Context Protocol (MCP) using the Micronaut framework.</p>
<p>We will use Micronaut 4.8 and Java 21.
You can find Micronaut&rsquo;s documentation here: <a href="https://docs.micronaut.io/4.8.11/guide/">https://docs.micronaut.io/4.8.11/guide/</a></p>
<p>The details of the Model Context Protocol (MCP) can be found here:
<a href="https://modelcontextprotocol.io/llms-full.txt">https://modelcontextprotocol.io/llms-full.txt</a></p>
<p>For the client, we will use LangChain4j as shown in this article:
<a href="https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/">https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/</a></p>
<p>You can find all the code of the LangChain4j MCP client support in the attached file.</p>
<p>[!INFO] Gitingest
&#x1f4ce; <em>[gitingest of the LangChain4j MCP client code]</em></p>
<p>You can reuse the Java classes of the LangChain4j MCP client to implement the MCP server support with Micronaut.</p>
<p>Implement a simple MCP server to let MCP clients request the weather forecast. Return fake data like <code>{&quot;forecast&quot;: &quot;sunny&quot;}</code></p></blockquote>
<p>The trick here was to feed the whole MCP specification thanks to the <code>llms-full.txt</code> file,
the whole Micronaut single-page documentation, and also the LangChain4j MCP client source code in attachment (via <a href="https://gitingest.com/">gitingest</a>).</p>
<p>Did it work in one shot? Actually, no.
Because my client wouldn&rsquo;t connect to it somehow, I figured that it wasn&rsquo;t actually using Server-Sent Events.
So I sent a follow-up prompt:</p>
<blockquote>
<p>The Micronaut controller must use HTTP Server Sent Events, as this is what the MCP protocol mandates for MCP remote servers. Please update the controller to use SSE.</p></blockquote>
<p>Then I had a running server.</p>
<p>Gemini created an <a href="https://github.com/glaforge/langchain4j-micronaut-mcp/blob/main/src/main/java/mcp/server/SseBroadcaster.java"><code>SseBroadcaster</code></a> class which handles the Server-Sent Event handling,
thanks to Reactor&rsquo;s <code>Publisher</code>, <code>Flux</code>, and <code>Sinks</code>, and Micronaut&rsquo;s <code>JsonMapper</code> and SSE support.</p>
<p>It handles the various JSON-RPC operations (<code>initialize</code>, <code>notifications/initialized</code>, <code>tools/list</code>, <code>tools/call</code>, and <code>ping</code>)
in the <a href="https://github.com/glaforge/langchain4j-micronaut-mcp/blob/main/src/main/java/mcp/server/PostController.java"><code>PostController</code></a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>McpResponse<span style="color:#bbb"> </span><span style="color:#06287e">processRequest</span>(McpRequest<span style="color:#bbb"> </span>request)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// --- Same logic as before to generate the McpResponse object ---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">switch</span><span style="color:#bbb"> </span>(request.<span style="color:#4070a0">method</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;initialize&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Handling initialize request&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>InitializeResult<span style="color:#bbb"> </span>initResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InitializeResult(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ServerCapabilities());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span>initResult);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;notifications/initialized&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Received initialized notification&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// This is a notification FROM the client. MCP spec says notifications</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// don&#39;t have responses. So we return null here, and the POST handler</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic">// will just return HTTP OK.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;tools/list&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Handling tools/list request&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>ToolSpecificationData<span style="color:#bbb"> </span>weatherTool<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ToolSpecificationData(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>WEATHER_TOOL_NAME,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;Gets the current weather forecast.&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InputSchema(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#4070a0">&#34;object&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;location&#34;</span>,<span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#4070a0">&#34;type&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#4070a0">&#34;description&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Location to get the weather for&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;location&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">false</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>ListToolsResult<span style="color:#bbb"> </span>listResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ListToolsResult(List.<span style="color:#4070a0">of</span>(weatherTool));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span>listResult);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;tools/call&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Handling tools/call request&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(request.<span style="color:#4070a0">params</span>()<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span>request.<span style="color:#4070a0">params</span>().<span style="color:#4070a0">has</span>(<span style="color:#4070a0">&#34;name&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>String<span style="color:#bbb"> </span>toolName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>request.<span style="color:#4070a0">params</span>().<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;name&#34;</span>).<span style="color:#4070a0">asText</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(WEATHER_TOOL_NAME.<span style="color:#4070a0">equals</span>(toolName))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Executing tool: {}&#34;</span>,<span style="color:#bbb"> </span>toolName);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>TextContentData<span style="color:#bbb"> </span>textContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextContentData(FAKE_WEATHER_JSON);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>CallToolResult<span style="color:#bbb"> </span>callResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>CallToolResult(List.<span style="color:#4070a0">of</span>(textContent));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span>callResult);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>log.<span style="color:#4070a0">warn</span>(<span style="color:#4070a0">&#34;Unknown tool requested: {}&#34;</span>,<span style="color:#bbb"> </span>toolName);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpError(<span style="color:#666">-</span>32601,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Method not found: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>toolName));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>log.<span style="color:#4070a0">error</span>(<span style="color:#4070a0">&#34;Invalid tools/call request: Missing &#39;name&#39; in params&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpError(<span style="color:#666">-</span>32602,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Invalid params for tools/call&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;ping&#34;</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Handling ping request&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span>Collections.<span style="color:#4070a0">emptyMap</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">default</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>log.<span style="color:#4070a0">warn</span>(<span style="color:#4070a0">&#34;Unsupported MCP method: {}&#34;</span>,<span style="color:#bbb"> </span>request.<span style="color:#4070a0">method</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpResponse(request.<span style="color:#4070a0">id</span>(),<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpError(<span style="color:#666">-</span>32601,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Method not found: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>request.<span style="color:#4070a0">method</span>()));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="from-vibe-coding-to-a-more-classical-ai-assisted-approach">From vibe coding, to a more classical AI-assisted approach</h2>
<p>The vibe coding part ended somewhere here, as I then went on to make a few tweaks here and there to the code base.
When you&rsquo;re a developer, you can&rsquo;t resist tweaking a few things here and there, right?</p>
<p>But I continued the journey also with the help of Gemini, but via <a href="https://codeassist.google/">Gemini Code Assist</a> within IntelliJ IDEA.</p>
<p>I reused my MCP client from my recent <a href="https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/">MCP article</a>,
but I asked Gemini Code Assist to transform the Java class into a proper JUnit test.
Since the Micronaut documentation is still part of the context of the conversation, thanks to Gemini&rsquo;s huge context window, it did again a great job at converting my code into a proper Micronaut running unit test, launching an embedded server.</p>
<p>I like that Gemini created three test methods: to test that the server can reply to some simple greeting message, then can list and the available MCP tools at the disposal of the clients:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Test</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">testListTools</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Testing listTools...&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>assertDoesNotThrow(()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>List<span style="color:#666">&lt;</span>dev.<span style="color:#4070a0">langchain4j</span>.<span style="color:#4070a0">agent</span>.<span style="color:#4070a0">tool</span>.<span style="color:#4070a0">ToolSpecification</span><span style="color:#666">&gt;</span><span style="color:#bbb"> </span>tools<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>mcpClient.<span style="color:#4070a0">listTools</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>assertNotNull(tools,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Tool list should not be null&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>assertFalse(tools.<span style="color:#4070a0">isEmpty</span>(),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Tool list should not be empty&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Add more specific assertions if needed, e.g., check tool names</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>assertTrue(tools.<span style="color:#4070a0">stream</span>().<span style="color:#4070a0">anyMatch</span>(t<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;getWeatherForecast&#34;</span>.<span style="color:#4070a0">equals</span>(t.<span style="color:#4070a0">name</span>())),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;Should find the &#39;getWeatherForecast&#39; tool&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;listTools returned: {}&#34;</span>,<span style="color:#bbb"> </span>tools);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>},<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Listing tools should not throw an exception&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And finally is able to reply to a weather request by returning some dummy weather data:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Test</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">testWeatherRequest</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Testing weather request...&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>question<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;What&#39;s the weather like in Paris today?&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>assertDoesNotThrow(()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>weatherAssistant.<span style="color:#4070a0">request</span>(question),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Weather request should not throw an exception&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Question: {}&#34;</span>,<span style="color:#bbb"> </span>question);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>log.<span style="color:#4070a0">info</span>(<span style="color:#4070a0">&#34;Response: {}&#34;</span>,<span style="color:#bbb"> </span>response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>assertNotNull(response,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Response should not be null&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>assertFalse(response.<span style="color:#4070a0">isBlank</span>(),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Response should not be blank&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Check if the response likely contains the mocked forecast</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>assertTrue(response.<span style="color:#4070a0">toLowerCase</span>().<span style="color:#4070a0">contains</span>(<span style="color:#4070a0">&#34;sunny&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Response should contain the weather information (sunny)&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="now-the-code">Now, the code!</h2>
<p>As you made it till the end, you&rsquo;ll get a reward: I pushed the code in this <a href="https://github.com/glaforge/langchain4j-micronaut-mcp">Github repository</a>!
The project doesn&rsquo;t implement all the bells and whistles of the MCP specification (like prompts, resources, sampling, etc.)
but it&rsquo;s certainly a starting point if you want to write your own MCP server with Micronaut.
Since LangChain4j released some <a href="https://github.com/langchain4j/langchain4j/releases/tag/1.0.0-rc1">new versions today</a>, I updated the dependencies to use the latest and greatest LangChain4j.</p>
<p>Going further, I&rsquo;d love to see Micronaut offer a dedicated MCP server module, to make it easier to implement MCP servers, with some clever annotations, to streamline the whole process.
Fingers crossed &#x1f91e;</p>
<p>And of course, once you&rsquo;re happy with your MCP server implementation, the extra step is to deploy the MCP server to Google&rsquo;s <a href="http://cloud.run/">Cloud Run</a>, like I explained in this article about the
<a href="https://glaforge.dev/posts/2022/10/24/build-deploy-java-17-apps-on-cloud-run-with-cloud-native-buildpacks-on-temurin/">various ways to deploy Micronaut apps to Cloud Run</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>MCP Client and Server with the Java MCP SDK and LangChain4j</title><link>https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/</link><pubDate>Fri, 04 Apr 2025 19:39:58 +0200</pubDate><guid>https://glaforge.dev/posts/2025/04/04/mcp-client-and-server-with-java-mcp-sdk-and-langchain4j/</guid><description>&lt;p>&lt;a href="https://modelcontextprotocol.io/introduction">MCP&lt;/a> (Model Context Protocol) is making a buzz these days!
MCP is a protocol invented &lt;a href="https://www.anthropic.com/news/model-context-protocol">last November&lt;/a> by Anthropic,
integrated in Claude Desktop and in more and more tools and frameworks,
to &lt;strong>expand LLMs capabilities&lt;/strong> by &lt;strong>giving them access to various external tools&lt;/strong> and functions.&lt;/p>
&lt;blockquote>
&lt;p>My colleague &lt;a href="https://x.com/_philschmid">Philipp Schmid&lt;/a> gave a great
&lt;a href="https://www.philschmid.de/mcp-introduction">introduction to MCP&lt;/a> recently,
so if you want to learn more about MCP, this is the place for you.&lt;/p>&lt;/blockquote>
&lt;p>In this article, I&amp;rsquo;d like to guide you through the implementation of an MCP server, and an MCP client, in Java.
As I&amp;rsquo;m contributing to &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>, I&amp;rsquo;ll be using LangChain4j&amp;rsquo;s &lt;code>mcp&lt;/code> module for the client.&lt;/p></description><content:encoded>
<![CDATA[<p><a href="https://modelcontextprotocol.io/introduction">MCP</a> (Model Context Protocol) is making a buzz these days!
MCP is a protocol invented <a href="https://www.anthropic.com/news/model-context-protocol">last November</a> by Anthropic,
integrated in Claude Desktop and in more and more tools and frameworks,
to <strong>expand LLMs capabilities</strong> by <strong>giving them access to various external tools</strong> and functions.</p>
<blockquote>
<p>My colleague <a href="https://x.com/_philschmid">Philipp Schmid</a> gave a great
<a href="https://www.philschmid.de/mcp-introduction">introduction to MCP</a> recently,
so if you want to learn more about MCP, this is the place for you.</p></blockquote>
<p>In this article, I&rsquo;d like to guide you through the implementation of an MCP server, and an MCP client, in Java.
As I&rsquo;m contributing to <a href="https://docs.langchain4j.dev/">LangChain4j</a>, I&rsquo;ll be using LangChain4j&rsquo;s <code>mcp</code> module for the client.</p>
<p>For the server, it&rsquo;s possible to use <a href="https://quarkus.io/blog/mcp-server/">Quarkus</a> or
<a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html">Spring Boot</a>.
But Christian Tsolov, who built the MCP library used by Spring Boot (which is also the official <a href="https://github.com/modelcontextprotocol/java-sdk">Java SDK</a> promoted by the Model Context Protocol project), recently <a href="https://x.com/christzolov/status/1906341689142243792">tweeted</a> that <strong>the MCP reference implementation can also be used standalone</strong> without a mandatory framework:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">🚀 The MCP Java SDK core module provides default STDIO &amp; SSE client/server transports - no external web frameworks needed!<br><br>Build MCP clients/servers in plain Java with both Async (Reactor) &amp; Sync APIs. Spring support is optional.<br><br>🔗 Minimal deps: <a href="https://t.co/9uW5vgMqqh">https://t.co/9uW5vgMqqh</a> <a href="https://t.co/aHLBHuXOUz">pic.twitter.com/aHLBHuXOUz</a></p>&mdash; Christian Tzolov🇧🇬🇪🇺🇺🇦 🦋@tzolov.bsky.social (@christzolov) <a href="https://twitter.com/christzolov/status/1906341689142243792?ref_src=twsrc%5Etfw">March 30, 2025</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<h2 id="developing-the-mcp-server">Developing the MCP server</h2>
<p>For the server, I need the reference implementation dependency, as well as some Jetty JARs (or the servlet container of your choice)
to expose an HTTP Server-Sent Event endpoint (you can also create <code>stdio</code> servers too):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>io.modelcontextprotocol.sdk<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>mcp<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.8.1<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.eclipse.jetty<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>jetty-server<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;version&gt;</span>12.0.18<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.eclipse.jetty.ee10<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>jetty-ee10-servlet<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;version&gt;</span>12.0.18<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>I&rsquo;m using Jetty here, as I want to expose the HTTP SSE endpoint as a servlet.</p>
<p>The first thing needed is to create an HTTP servlet SSE transport provider.
I&rsquo;ll expose the <code>/sse</code> endpoint that the client will be able to access:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>HttpServletSseServerTransportProvider<span style="color:#bbb"> </span>transportProvider<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>HttpServletSseServerTransportProvider(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ObjectMapper(),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;/&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;/sse&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The MCP reference implementation allows you to implement async or sync servers.
I&rsquo;m going with a synchronous one, as it&rsquo;s easier to implement:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>McpSyncServer<span style="color:#bbb"> </span>syncServer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>McpServer.<span style="color:#4070a0">sync</span>(transportProvider)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">serverInfo</span>(<span style="color:#4070a0">&#34;custom-server&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;0.0.1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">capabilities</span>(McpSchema.<span style="color:#4070a0">ServerCapabilities</span>.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">resources</span>(<span style="color:#007020;font-weight:bold">false</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">prompts</span>(<span style="color:#007020;font-weight:bold">false</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>MCP servers can expose:</p>
<ul>
<li>tools</li>
<li>resources</li>
<li>prompts</li>
</ul>
<p>In my case, I&rsquo;m just interested in exposing a tool.
I&rsquo;ll go with a classical weather tool, which is a bit like the <em>Hello World</em> of LLM function calling!</p>
<p>Let&rsquo;s define our <code>weather-forecast</code> tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>McpServerFeatures.<span style="color:#4070a0">SyncToolSpecification</span><span style="color:#bbb"> </span>syncToolSpecification<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpServerFeatures.<span style="color:#4070a0">SyncToolSpecification</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpSchema.<span style="color:#4070a0">Tool</span>(<span style="color:#4070a0">&#34;weather-forecast&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;gives today&#39;s weather forecast for a given location&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">              &#34;type&#34;: &#34;object&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">              &#34;properties&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                &#34;location&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                  &#34;type&#34;: &#34;string&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">              },
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">              &#34;required&#34;: [&#34;location&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>(mcpSyncServerExchange,<span style="color:#bbb"> </span>stringObjectMap)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpSchema.<span style="color:#4070a0">CallToolResult</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>List.<span style="color:#4070a0">of</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>McpSchema.<span style="color:#4070a0">TextContent</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                    {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                        &#34;location&#34;: &#34;Paris&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                        &#34;forecast&#34;: &#34;Nice and sunny weather, with clear blue sky, and temperature of 17°C.&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                    }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                    &#34;&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>)),<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>syncServer.<span style="color:#4070a0">addTool</span>(syncToolSpecification);<span style="color:#bbb">
</span></span></span></code></pre></div><p>I defined the tool, with a description (which helps LLMs know which tools to invoke for which use case).
The schema of the input is described as a JSON string (as I struggled a bit to find the correct way to create the schema programmatically).</p>
<p>Then I defined the lambda function that is called when the tool is invoked.
I&rsquo;m returning a JSON object that contains the <code>location</code> and the <code>forecast</code>.</p>
<p>And I&rsquo;m done with the MCP server implementation!</p>
<p>But now, I need to expose this server thanks to the Jetty Servlet container.</p>
<p>Let&rsquo;s define a new Jetty server, connector, servlet context handler, export the servlet, and start the server:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>QueuedThreadPool<span style="color:#bbb"> </span>threadPool<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>QueuedThreadPool();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>threadPool.<span style="color:#4070a0">setName</span>(<span style="color:#4070a0">&#34;server&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Server<span style="color:#bbb"> </span>server<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Server(threadPool);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ServerConnector<span style="color:#bbb"> </span>connector<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ServerConnector(server);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>connector.<span style="color:#4070a0">setPort</span>(45450);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>server.<span style="color:#4070a0">addConnector</span>(connector);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ServletContextHandler<span style="color:#bbb"> </span>context<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ServletContextHandler();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>context.<span style="color:#4070a0">setContextPath</span>(<span style="color:#4070a0">&#34;/&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>context.<span style="color:#4070a0">addServlet</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ServletHolder(transportProvider),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;/*&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>server.<span style="color:#4070a0">setHandler</span>(context);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>server.<span style="color:#4070a0">start</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now if you run this code, your MCP server function will be waiting for its first invocations.</p>
<h2 id="developing-the-mcp-client-with-langchain4j">Developing the MCP client with LangChain4j</h2>
<p>For the MCP client, I use the <a href="https://docs.langchain4j.dev/tutorials/mcp/">LangChain4j MCP module</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>dev.langchain4j<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>langchain4j-mcp<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#062873;font-weight:bold">&lt;version&gt;</span>${langchain4j.version}<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>Since I&rsquo;m using Gemini, I need some dependencies for the Vertex AI Gemini model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>dev.langchain4j<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>langchain4j<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>${langchain4j.version}<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>dev.langchain4j<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>langchain4j-vertex-ai-gemini<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>${langchain4j.version}<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>Let&rsquo;s get started with instantiating our Gemini 2.0 Flash lite model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(<span style="color:#4070a0">&#34;genai-playground24&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash-lite&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>I&rsquo;m defining an <code>McpTransport</code> pointing at my local Jetty SSE server:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>McpTransport<span style="color:#bbb"> </span>transport<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>HttpMcpTransport.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">sseUrl</span>(<span style="color:#4070a0">&#34;http://0.0.0.0:45450/sse&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s create an MCP client using that transport:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>McpClient<span style="color:#bbb"> </span>mcpClient<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>DefaultMcpClient.<span style="color:#4070a0">Builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">transport</span>(transport)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>And a tool provider that will expose just the <code>weather-forecast</code> tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ToolProvider<span style="color:#bbb"> </span>toolProvider<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>McpToolProvider.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">mcpClients</span>(List.<span style="color:#4070a0">of</span>(mcpClient))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>You can list the available tools as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>mcpClient.<span style="color:#4070a0">listTools</span>().<span style="color:#4070a0">forEach</span>(System.<span style="color:#4070a0">out</span>::println);<span style="color:#bbb">
</span></span></span></code></pre></div><p>As I&rsquo;m going to create a LangChain4j AI service, I need a contract.
This will be the following simple interface:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">WeatherAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">request</span>(String<span style="color:#bbb"> </span>message);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now, it&rsquo;s time to instantiate that service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>WeatherAssistant<span style="color:#bbb"> </span>meteo<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(WeatherAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatLanguageModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toolProvider</span>(toolProvider)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>And we can now chat with that service, asking mundane questions, as well as weather related questions that will be invoking the <code>weather-forecast</code> tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;Hello!&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;What&#39;s the weather like in Paris today?&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>).<span style="color:#4070a0">forEach</span>((String<span style="color:#bbb"> </span>q)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(blue(q));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(green(meteo.<span style="color:#4070a0">request</span>(q)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>It will print something like:</p>
<pre tabindex="0"><code>Hello!
The weather in Paris is sunny today, with a clear blue sky,
and a temperature of 17°C.
</code></pre><h2 id="conclusion">Conclusion</h2>
<p>In this article, we&rsquo;ve seen:</p>
<ul>
<li>how to <strong>create an MCP server</strong>, using the official <strong>Java MCP SDK</strong>,</li>
<li>and how to <strong>create an MCP client</strong> with <strong>LangChain4j</strong>.</li>
</ul>
<p>We&rsquo;ve seen how to define a tool, and how to expose it via an HTTP SSE endpoint.
We&rsquo;ve also seen how to use the MCP client to invoke that tool, and how to integrate it in a LangChain4j AI service.</p>
<p>This is a great way to expand the capabilities of LLMs, by giving them access to external tools and functions.
And it shows that you can interact with any MCP server with LangChain4j.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Quick Tip: Clearing disk space in Cloud Shell</title><link>https://glaforge.dev/posts/2025/03/08/quick-tip-clearing-disk-space-in-cloud-shell/</link><pubDate>Sat, 08 Mar 2025 16:29:41 +0100</pubDate><guid>https://glaforge.dev/posts/2025/03/08/quick-tip-clearing-disk-space-in-cloud-shell/</guid><description>&lt;p>Right in the middle of a &lt;a href="https://glaforge.dev/posts/2024/03/27/gemini-codelab-for-java-developers/">workshop&lt;/a> I was delivering, as I was launching Google Cloud console&amp;rsquo;s &lt;a href="https://cloud.google.com/shell/docs">Cloud Shell&lt;/a> environment, I received the dreaded warning message: &lt;code>no space left on device&lt;/code>.&lt;/p>
&lt;p>And indeed, I didn&amp;rsquo;t have much space left, and Cloud Shell was reminding me it was high time I clean up the mess! Fortunately, the shell gives a nice hint, with a pointer to this &lt;a href="https://cloud.google.com/shell/docs/quotas-limits#clearing_disk_space">documentation page&lt;/a> with advice on how to reclaim space.&lt;/p></description><content:encoded>
<![CDATA[<p>Right in the middle of a <a href="https://glaforge.dev/posts/2024/03/27/gemini-codelab-for-java-developers/">workshop</a> I was delivering, as I was launching Google Cloud console&rsquo;s <a href="https://cloud.google.com/shell/docs">Cloud Shell</a> environment, I received the dreaded warning message: <code>no space left on device</code>.</p>
<p>And indeed, I didn&rsquo;t have much space left, and Cloud Shell was reminding me it was high time I clean up the mess! Fortunately, the shell gives a nice hint, with a pointer to this <a href="https://cloud.google.com/shell/docs/quotas-limits#clearing_disk_space">documentation page</a> with advice on how to reclaim space.</p>
<p>The document suggests to run the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>du -hs <span style="color:#007020;font-weight:bold">$(</span>ls -A<span style="color:#007020;font-weight:bold">)</span>
</span></span></code></pre></div><p>This command shows the space each file uses within each sub-directory.</p>
<p>Here&rsquo;s the output I got after having cleaned up the many caches, directories and projects I didn&rsquo;t need anymore:</p>
<pre tabindex="0"><code>20K     .bash_history
4.0K    .bash_logout
4.0K    .bashrc
20M     .cache
320M    .codeoss
112K    .config
8.0K    .docker
247M    gemini-workshop-for-java-developers
4.0K    .gitconfig
341M    .gradle
12K     .gsutil
4.0K    .lesshst
16K     .npm
4.0K    .profile
0       .python_history
4.0K    README-cloudshell.txt
8.0K    .redhat
4.0K    .ssh
0       .sudo_as_admin_successful
8.0K    .vscode
</code></pre><p>You quickly see directories (like <code>.codeoss</code> or my <code>gemini-workshop-for-java-developers</code>) that fill up the most space, and you can go after each of those repositories and launch some <code>rm -Rf some-directory</code> commands here and there. Of course, pay attention to what you&rsquo;re going to delete, as this is irreversible!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>LLMs.txt to help LLMs grok your content</title><link>https://glaforge.dev/posts/2025/03/03/llms-txt-to-help-llms-grok-your-content/</link><pubDate>Mon, 03 Mar 2025 11:03:35 +0100</pubDate><guid>https://glaforge.dev/posts/2025/03/03/llms-txt-to-help-llms-grok-your-content/</guid><description>&lt;p>Since I started my career, I&amp;rsquo;ve been sharing what I&amp;rsquo;ve learned along the way in this blog.
It makes me happy when developers find solutions to their problems, or discover new things, thanks to articles I&amp;rsquo;ve written here.
So it&amp;rsquo;s important for me that readers are able to find those posts.
Of course, my blog is indexed by search engines, and people usually find about it from Google or other engines, or they discover it via the links I share on social media.
But with LLM powered tools (like Gemini, ChatGPT, Claude, etc.) you can make your content more easily &lt;em>grokkable&lt;/em> by such tools.&lt;/p></description><content:encoded>
<![CDATA[<p>Since I started my career, I&rsquo;ve been sharing what I&rsquo;ve learned along the way in this blog.
It makes me happy when developers find solutions to their problems, or discover new things, thanks to articles I&rsquo;ve written here.
So it&rsquo;s important for me that readers are able to find those posts.
Of course, my blog is indexed by search engines, and people usually find about it from Google or other engines, or they discover it via the links I share on social media.
But with LLM powered tools (like Gemini, ChatGPT, Claude, etc.) you can make your content more easily <em>grokkable</em> by such tools.</p>
<p>A <a href="https://llmstxt.org/">proposal</a> emerged last year to add a new file websites, <code>llms.txt</code>, which is a Markdown document that lists all the posts, pages, articles of that website. In a way, it&rsquo;s similar to other kind of indexing-related files like <code>robots.txt</code> or <code>sitemap.xml</code>. But this time, the idea is to offer the content of your documentation, website, or blog, as Markdown, easily <em>grokkable</em> by LLM powered tools.</p>
<h2 id="updating-hugo-to-generate-llmstxt-files">Updating Hugo to generate LLMs.txt files</h2>
<p>This blog post is powered by <a href="https://gohugo.io/">Hugo</a>, a static site generator.
I&rsquo;ll explain how I added 2 new files to my blog:</p>
<ul>
<li><a href="https://glaforge.dev/llms.txt">llms.txt</a> — which references links to all the blog posts, a bit like a sitemap.</li>
<li><a href="https://glaforge.dev/llms-full.txt">llms-full.txt</a> — which contains all the blog post articles in one giant file.</li>
</ul>
<p>In <code>config.toml</code>, I added two new output formats:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[outputFormats.llms]
</span></span><span style="display:flex;"><span>baseName = <span style="color:#4070a0">&#34;llms&#34;</span>
</span></span><span style="display:flex;"><span>mediaType = <span style="color:#4070a0">&#34;text/plain&#34;</span>
</span></span><span style="display:flex;"><span>isPlainText = <span style="color:#007020;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[outputFormats.llms-full]
</span></span><span style="display:flex;"><span>baseName = <span style="color:#4070a0">&#34;llms-full&#34;</span>
</span></span><span style="display:flex;"><span>mediaType = <span style="color:#4070a0">&#34;text/plain&#34;</span>
</span></span><span style="display:flex;"><span>isPlainText = <span style="color:#007020;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[outputs]
</span></span><span style="display:flex;"><span>home = [<span style="color:#4070a0">&#34;HTML&#34;</span>, <span style="color:#4070a0">&#34;RSS&#34;</span>, <span style="color:#4070a0">&#34;llms&#34;</span>, <span style="color:#4070a0">&#34;llms-full&#34;</span>]
</span></span></code></pre></div><p>And in my custom theme <code>layouts\_default</code> folder, I added two templates for those two files:</p>
<ul>
<li><code>index.llms.txt</code> references all the articles</li>
</ul>
<pre tabindex="0"><code># Blog of {{ .Site.Title }}

&gt; {{ .Site.Params.Description }}

You can find [more information about this site here](/about).

## All articles
{{ range .Site.RegularPages.GroupByDate &#34;2006&#34; }}

### {{ .Key }}
{{ range .Pages }}
- [{{- .Title -}}]({{- .Site.BaseURL -}}{{- .RelPermalink -}})
{{- end -}}
{{ end }}
</code></pre><ul>
<li><code>index.llms-full.txt</code> provides the full content of all the articles</li>
</ul>
<pre tabindex="0"><code>{{ range .Site.RegularPages }}
# {{ .Title }}

{{ .RawContent }}
{{- end -}}
</code></pre><h2 id="now-what-can-you-do-with-that">Now what can you do with that?</h2>
<p>In my recent article on <a href="https://glaforge.dev/posts/2025/02/15/the-power-of-large-context-windows-for-your-documentation-efforts/">the power of large context windows for your documentation efforts</a>) I mentioned that you could feed all your content into an LLM prompt and have a discussion with it. This is the same idea I&rsquo;m following here. Thanks to the full export of all my articles, I can ask questions about everything I&rsquo;ve written so far.</p>
<p>Let&rsquo;s go to <a href="https://aistudio.google.com/">Google AI Studio</a> and ask Gemini, and its huge token context window!</p>
<p><figure>
  <a href="#img-eb43d4f995ef4c8132b7d3c2485fe331">
    <img src="/img/gemini/llms-txt-blog-content.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-eb43d4f995ef4c8132b7d3c2485fe331">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/llms-txt-blog-content.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>When I asked about the main topics of this blog, the above screenshot shows Gemini&rsquo;s answer, highlighting my content about <a href="https://groovy-lang.org/">Apache Groovy</a>, Google Cloud Platform, generative AI, Web APIs, software development best practices.</p>
<p>Look at the number of tokens! <strong>My whole blog is about 682k tokens</strong>! Only Gemini could <em>grok</em> all of that and make sense of it.</p>
<p>As a follow up question, I also asked <em>&ldquo;Can you detail more the articles about Generative AI?&rdquo;</em>, and Gemini highlighted some of my recent articles and summarized them all:</p>
<blockquote>
<p>Okay, based on the file, here&rsquo;s a breakdown of the articles specifically about Generative AI:</p>
<ul>
<li><strong>The power of large context windows for your documentation efforts:</strong> This article explores using LLMs with large context windows (like Gemini) to chat with reference documentation, to assist in tasks like creating new modules for LangChain4j. It discusses using &ldquo;gitingest&rdquo; to convert a GitHub project into a large text file that can be fed to an LLM.</li>
<li><strong>A Generative AI Agent with a real declarative workflow:</strong> This piece compares an imperative Java-based AI agent with a declarative approach using Google Cloud Workflows. The agent generates short sci-fi stories, and the article details the pros and cons of each method.</li>
<li><strong>An AI agent to generate short sci-fi stories:</strong> This project details how to build an AI-powered short story generation agent using Java, LangChain4j, Gemini, and Imagen 3, deployed on Cloud Run jobs. It contrasts explicit vs. autonomous agent workflows.</li>
<li><strong>Analyzing trends and topics from Bluesky&rsquo;s Firehose with generative AI:</strong> This article describes a project that analyzes the Bluesky social media stream, using vector embeddings and clustering to identify trending topics, and using Gemini to generate summaries of these topics.</li>
<li><strong>Let&rsquo;s think with Gemini Flash 2.0&rsquo;s experimental thinking mode and LangChain4j:</strong> This article explores the new &ldquo;thinking mode&rdquo; of Gemini 2.0 Flash, using LangChain4j to solve riddles and multimodal problems.</li>
<li><strong>Detecting objects with Gemini 2.0 and LangChain4j:</strong> This article discusses using Gemini 2.0&rsquo;s object detection capabilities with LangChain4j to recognize playing cards in images.</li>
<li><strong>Semantic code search for Programming Idioms with LangChain4j and Vertex AI embedding models:</strong> Explores the use of Vertex AI embedding models for semantic code search on the Programming Idioms community website. It focuses on using code retrieval task types and LangChain4j for efficient retrieval of code examples.</li>
<li><strong>Redacting sensitive information when using Generative AI models:</strong> Details the importance of redacting Personally Identifiable Information (PII) before sending user messages to LLMs, using the Google Cloud Data Loss Prevention (DLP) API.</li>
<li><strong>Data extraction: The many ways to get LLMs to spit JSON content:</strong> This article reviews different techniques to make LLMs generate JSON output and extract data from text, using Gemini and LangChain4j.</li>
</ul>
<p>These articles demonstrate a clear focus on using various LLMs (primarily Gemini) with tools like LangChain4j and Google Cloud services to address different AI-related problems.</p></blockquote>
<p>Not only did it summarize all those recent articles, but I also like the final sentence that outlines what most of those articles have in common: They are primarily using the Gemini model, with the <a href="https://docs.langchain4j.dev/">LangChain4j framework</a>, and running on Google Cloud Platform.</p>
<p>When asked to dive deeper on what is so special about LangChain4j, for instance, then again it highlighted the key features of the framework (that I demonstrated in my articles):</p>
<blockquote>
<p>LangChain4j appears to be a central tool for building Generative AI solutions in the provided articles, so I collected a few pieces of information to explain what&rsquo;s so special about it:</p>
<ul>
<li><strong>Java-Centric Approach:</strong> LangChain4j is a Java library. In a field dominated by Python, LangChain4j is a key tool for Java developers to leverage generative AI without having to switch to a different programming language or environment.</li>
<li><strong>LLM Orchestration:</strong> LangChain4j facilitates the orchestration of different components in an AI pipeline, from loading documents and splitting them into chunks, to calculate their embedding vectors, storing them in vector databases, and retrieve them to be passed to the LLM, and the augment the LLM prompt with context from those vectors. This reduces the complexity of AI application development.</li>
<li><strong>Integration with Google Cloud Services:</strong> LangChain4j has built-in integrations with Google Cloud&rsquo;s Vertex AI, Google AI Gemini, and Google Cloud Storage. This allows Java developers to easily use Google&rsquo;s LLMs, embedding models, and data storage solutions.</li>
<li><strong>Extensible and Customizable:</strong> The library offers a way to plug custom logic and algorithms into the different steps, and to define how the information should be used to interact with the LLM.</li>
<li><strong>Higher-Level Abstractions:</strong> LangChain4j provides higher-level abstractions for interacting with LLMs, such as the <code>AiServices</code> abstraction that returns type-safe objects, which allows developers to manipulate real Java objects, in a type-safe manner, instead of dealing with raw JSON strings.</li>
<li><strong>Capabilities like Tool Calling, Context Window, and the like:</strong> It integrates new LLM capabilities to improve the quality of the AI powered application&rsquo;s responses.</li>
<li><strong>Metadata Handling</strong>: It eases the handling of metadata in text segments, including filtering based on metadata during the retrieval phase.</li>
</ul>
<p>In the context of the articles, LangChain4j simplifies the implementation of complex tasks like:</p>
<ul>
<li>Creating chatbots that can answer questions based on a specific knowledge base (RAG).</li>
<li>Analyzing trends in social media data.</li>
<li>Generating structured content (e.g., JSON) from unstructured text.</li>
<li>Implementing semantic code search.</li>
</ul>
<p>LangChain4j&rsquo;s key value is that it allows Java developers to more easily build, experiment with, and deploy Generative AI applications using their existing skills and infrastructure, while abstracting away much of the underlying complexity.</p></blockquote>
<h2 id="thoughts-and-summary">Thoughts and summary</h2>
<p>For the past months, I&rsquo;ve been exploring various Retrieval Augmented Generation (RAG) techniques to improve the quality of answers for questions on heaps of documents and data, often chunked and stored in vector databases. However, RAG hits some limits when you want to ask more general questions, or to analyze some content more deeply, by understanding it in its entirety.</p>
<p>That&rsquo;s where LLMs like Gemini with large context windows help, as they can figure out the fine print, the relationships between various sections, see common patterns across articles. I believe we should take the <strong>best of both worlds, by finding relevant documents with a hybrid search approach mixing keyword and semantic searches, but then feeding LLMs not only with extracts but with whole documents</strong>.</p>
<p>The <code>llms.txt</code> initiative is interesting, and I hope it&rsquo;ll be adopted further in the future.
It will make it simpler for users to more easily reason about documentation, blogs, etc. via LLMs.
As LLMs are getting bigger context windows, the discussion with whole docs or sites become richer, with full understanding of the context of the corpus of documents.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Pretty-print Markdown on the console</title><link>https://glaforge.dev/posts/2025/02/27/pretty-print-markdown-on-the-console/</link><pubDate>Thu, 27 Feb 2025 17:01:26 +0100</pubDate><guid>https://glaforge.dev/posts/2025/02/27/pretty-print-markdown-on-the-console/</guid><description>&lt;p>With Large Language Models loving to output Markdown responses, I&amp;rsquo;ve been wanting to display those Markdown snippets nicely in the console, when developing some LLM-powered apps and experiments.
At first, I thought I could use a Markdown parser library, and implement some kind of output formatter to display the text nicely, taking advantage of &lt;a href="https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797">ANSI color codes and formats&lt;/a>.
However it felt a bit over-engineered, so I thought &lt;em>&amp;ldquo;hey, why not just use some simple regular expressions!&amp;rdquo;&lt;/em> (and now you&amp;rsquo;ll tell me I have a second problem with regexes)&lt;/p></description><content:encoded>
<![CDATA[<p>With Large Language Models loving to output Markdown responses, I&rsquo;ve been wanting to display those Markdown snippets nicely in the console, when developing some LLM-powered apps and experiments.
At first, I thought I could use a Markdown parser library, and implement some kind of output formatter to display the text nicely, taking advantage of <a href="https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797">ANSI color codes and formats</a>.
However it felt a bit over-engineered, so I thought <em>&ldquo;hey, why not just use some simple regular expressions!&rdquo;</em> (and now you&rsquo;ll tell me I have a second problem with regexes)</p>
<p>In this blog post, I just want to share the few lines of code I&rsquo;ve added in a utility class to output Markdown content nicely.
It&rsquo;s not covering all the bells &amp; whistles of the Markdown syntax (in particular things like Github flavored extensions, like arrays, etc.) but it&rsquo;s good enough for my use case.</p>
<h2 id="markdown-syntax-highlighting-on-the-console">Markdown syntax highlighting on the console</h2>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">markdown</span>(String<span style="color:#bbb"> </span>md)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>md<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Bold</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\*\\*(.*?)\\*\\*&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[1m$1\u001B[0m&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Italic</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\*(.*?)\\*&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[3m$1\u001B[0m&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Underline</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;__(.*?)__&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[4m$1\u001B[0m&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Strikethrough</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;~~(.*?)~~&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[9m$1\u001B[0m&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Blockquote</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;(&gt; ?.*)&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[3m\u001B[34m\u001B[1m$1\u001B[22m\u001B[0m&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Lists (bold magenta number and bullet)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;([\\d]+\\.|-|\\*) (.*)&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[35m\u001B[1m$1\u001B[22m\u001B[0m $2&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Block code (black on gray)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;(?s)```(\\w+)?\\n(.*?)\\n```&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[3m\u001B[1m$1\u001B[22m\u001B[0m\n\u001B[57;107m$2\u001B[0m\n&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Inline code (black on gray)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;`(.*?)`&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[57;107m$1\u001B[0m&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Headers (cyan bold)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;(#{1,6}) (.*?)\n&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[36m\u001B[1m$1 $2\u001B[22m\u001B[0m\n&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Headers with a single line of text followed by 2 or more equal signs</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;(.*?\n={2,}\n)&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[36m\u001B[1m$1\u001B[22m\u001B[0m\n&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Headers with a single line of text followed by 2 or more dashes</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;(.*?\n-{2,}\n)&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[36m\u001B[1m$1\u001B[22m\u001B[0m\n&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Images (blue underlined)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;!\\[(.*?)]\\((.*?)\\)&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[34m$1\u001B[0m (\u001B[34m\u001B[4m$2\u001B[0m)&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Links (blue underlined)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;!?\\[(.*?)]\\((.*?)\\)&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;\u001B[34m$1\u001B[0m (\u001B[34m\u001B[4m$2\u001B[0m)&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><blockquote>
<p>This can easily be translated into other programming languages. Just be careful with the small differences in syntax of regular expressions.</p></blockquote>
<p>For the following Markdown text:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"># Main title
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>Big title
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold">## Subtitle
</span></span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>Small title
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"># Bold and italic
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>Some <span style="font-weight:bold">**bold text**</span>.
</span></span><span style="display:flex;"><span>Bits of <span style="font-style:italic">_italicized text_</span>.
</span></span><span style="display:flex;"><span>It&#39;s <span style="font-weight:bold">**underlined**</span>.
</span></span><span style="display:flex;"><span>And <span style="color:#a00000">~~striked through~~</span>.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold">## Links
</span></span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>A [<span style="color:#062873;font-weight:bold">link</span>](<span style="color:#4070a0">https://www.example.com</span>) to an article.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>![<span style="color:#062873;font-weight:bold">alt text</span>](<span style="color:#4070a0">image.jpg</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold">### Quoting
</span></span></span><span style="display:flex;"><span><span style="color:#800080;font-weight:bold"></span><span style="color:#007020;font-weight:bold">
</span></span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">&gt; </span><span style="font-style:italic">a quote of someone famous, potentially wrapping around multiple lines.
</span></span></span><span style="display:flex;"><span><span style="font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"># Lists
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">1.</span> First item
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">2.</span> Second item
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">3.</span> Third item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">-</span> First item
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">-</span> Second item
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">-</span> Third item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"># Code
</span></span></span><span style="display:flex;"><span><span style="color:#000080;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>Some inline <span style="color:#4070a0">`code`</span> inside a paragraph.
</span></span><span style="display:flex;"><span>Return type is <span style="color:#4070a0">`void`</span> and args are <span style="color:#4070a0">`String[]`</span>.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>A fenced code block:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#4070a0">```java
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">Hello</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Hello World!&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#4070a0">```</span>
</span></span></code></pre></div><p>On the console output, the above Markdow document would be rendered as follows:</p>
<p><figure>
  <a href="#img-42fc11d41b5353368ffcb5d24249b5d9">
    <img src="/img/misc/markdown-rendered-in-console.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-42fc11d41b5353368ffcb5d24249b5d9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/markdown-rendered-in-console.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Do you like this Markdown syntax highlighting?</p>
<h2 id="bonus-points">Bonus points</h2>
<p>At first, in my utility class, I only had methods for adding some colors in my program outputs.
Even if you don&rsquo;t use Markdown, those touches of color can be useful to differentiate key parts of your output.</p>
<p>So I created some methods for wrapping text in ANSI codes:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">red</span>(String<span style="color:#bbb"> </span>msg)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[31m&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>msg<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[0m&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">green</span>(String<span style="color:#bbb"> </span>msg)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[31m&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>msg<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[0m&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">bold</span>(String<span style="color:#bbb"> </span>msg)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[1m&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>msg<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[0m&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">italic</span>(String<span style="color:#bbb"> </span>msg)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[3m&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>msg<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\u001B[0m&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>You can combine them like in <code>bold(green(msg))</code>, add more colors, or even write some fancy rainbow text!
As long as the output is still readable, it&rsquo;s all fair game!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Advanced RAG — Sentence Window Retrieval</title><link>https://glaforge.dev/posts/2025/02/25/advanced-rag-sentence-window-retrieval/</link><pubDate>Tue, 25 Feb 2025 17:01:50 +0100</pubDate><guid>https://glaforge.dev/posts/2025/02/25/advanced-rag-sentence-window-retrieval/</guid><description>&lt;p>Retrieval Augmented Generation (RAG) is a great way to expand the knowledge of Large Language Models to let them know about your own data and documents. With RAG, LLMs can ground their answers on the information your provide, which reduces the chances of hallucinations.&lt;/p>
&lt;p>Implementing RAG is fairly trivial with a framework like &lt;a href="https://docs.langchain4j.dev/tutorials/rag">LangChain4j&lt;/a>. However, the results may not be on-par with your quality expectations. Often, you&amp;rsquo;ll need to further tweak different aspects of the RAG pipeline, like the document preparation phase (in particular docs chunking), or the retrieval phase to find the best information in your vector database.&lt;/p></description><content:encoded>
<![CDATA[<p>Retrieval Augmented Generation (RAG) is a great way to expand the knowledge of Large Language Models to let them know about your own data and documents. With RAG, LLMs can ground their answers on the information your provide, which reduces the chances of hallucinations.</p>
<p>Implementing RAG is fairly trivial with a framework like <a href="https://docs.langchain4j.dev/tutorials/rag">LangChain4j</a>. However, the results may not be on-par with your quality expectations. Often, you&rsquo;ll need to further tweak different aspects of the RAG pipeline, like the document preparation phase (in particular docs chunking), or the retrieval phase to find the best information in your vector database.</p>
<p>In this first article (hopefully of a series on advanced RAG techniques) I&rsquo;d like to explore an approach that may yield better results: <strong>sentence window retrieval</strong>, inspired by the technique described in this <a href="https://www.linkedin.com/pulse/sentence-window-retrieval-optimizing-llm-performance-rutam-bhagat-v24of/">article</a>.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Info</span>
      </div>
      <div class="admonition-content">
        <p>I&rsquo;ve explored many techniques in my <a href="https://glaforge.dev/talks/2024/10/14/advanced-rag-techniques/">advanced RAG techniques</a> presentation, if you feel like discovering other techniques that we&rsquo;ll explore in more details in this series.</p>
      </div>
    </div><h2 id="lets-step-back-to-naive-chunking">Let&rsquo;s step back to naive chunking</h2>
<p>First, why do we even split documents in smaller chunks?
We split documents into chunks in RAG because:</p>
<ul>
<li>It&rsquo;s easier to find the specific, relevant piece of information within a smaller chunk than a huge document.</li>
<li>Large Language Models have limited memory. Chunks allow us to feed them just the necessary context, instead of overwhelming them with the whole document.</li>
<li>Smaller chunks lead to more precise retrieval, delivering more accurate answers.</li>
</ul>
<p>The naive approach is to split in chunks of a certain amount of characters. For example, on the Wikipedia page of Berlin, a 100-character split might look as follows:</p>
<p><figure>
  <a href="#img-6e8c69d8a25a65da2b42a1f581e02c71">
    <img src="/img/rag/naive-chunk-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6e8c69d8a25a65da2b42a1f581e02c71">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/rag/naive-chunk-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>If a user asks the question <em>&ldquo;What is the population of Berlin?&rdquo;</em>, the number of inhabitants is split across two chunks. So neither the first, nor the second chunk would yield the correct information, for the LLM to generate an accurate answer.</p>
<p>An obvious improvement is to use overlapping chunks:</p>
<p><figure>
  <a href="#img-91622a57a4c116c7860a8af0a47317a5">
    <img src="/img/rag/naive-chunk-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-91622a57a4c116c7860a8af0a47317a5">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/rag/naive-chunk-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The red chunk and the orange chunk overlap: both contain the gray part as well. Which means that the second chunk contains the number (in full) we&rsquo;re interesteded in.</p>
<p>Another possible approach, to avoid splits and overlaps, is to chunk by sentences. After all, human beings write sentences for a good reason, because they bear information that represent a unit of semantic meaning.</p>
<p><figure>
  <a href="#img-5332a995f5f4be97256bd0ce94b47976">
    <img src="/img/rag/naive-chunk-3.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-5332a995f5f4be97256bd0ce94b47976">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/rag/naive-chunk-3.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>However, both the chunk with overlap example above, as well as the sentence split expose another flaw: Notice that the pronoun <code>its</code>, in the second chunk or the second sentence doesn&rsquo;t carry the information that it actually references <code>Berlin</code>. So the pronoun misses an important aspect of the sentence: this is a sentence about the population of Berlin. Not any other city.</p>
<p>An alternative may be to increase the size of the chunk, and/or the size of the overlap, to avoid information to be split across chunks (like the population figure), and to give more context about possible links between sentences (like our pronoun-city). However, the wider the chunks, the more diluted the semantic meaning in the resulting vector embeddings.</p>
<p>With more dillution, it&rsquo;s harder to have query vectors (the user prompt) match the chunks of texts with high similarity values.</p>
<h2 id="enters-sentence-window-retrieval">Enters sentence window retrieval</h2>
<p>The name of the technique comes from this <a href="https://www.linkedin.com/pulse/sentence-window-retrieval-optimizing-llm-performance-rutam-bhagat-v24of/">article</a> I mentioned.
But maybe it&rsquo;s not the best name we could find. Maybe something like <em>wider-context-sliding-window-embedding</em> would be more explicit, but that&rsquo;s a mouthful!</p>
<p>Let&rsquo;s have a look at this approach:</p>
<p><figure>
  <a href="#img-68511214847d786ce011edf6786e476a">
    <img src="/img/rag/naive-chunk-4.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-68511214847d786ce011edf6786e476a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/rag/naive-chunk-4.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The idea is as follows:</p>
<ul>
<li>We calculate vector embeddings for the sentence in dark green.</li>
<li>But we save the surrounding sentences in light green (for example, one sentence before, and two after).</li>
</ul>
<p>At retrieval time, the vector similarity calculation will match better with the dark green sentence (in spite of its missing <em>Berlin</em> aspect).
But the whole light + dark green context will be added in the prompt of the LLM, instead of the single sentence.</p>
<p>The advantages are that:</p>
<ul>
<li>We keep on carrying meaningful units of meaning with a few sentences, thus avoiding any key information cut between splits, and semantic dillution of bigger chunks.</li>
<li>It helps the LLM resolve links between pronouns and their related entity. The LLM knows that we&rsquo;re talking about Berlin here.</li>
</ul>
<h2 id="the-canonical-rag-implementation-in-langchain4j">The canonical RAG implementation in LangChain4j</h2>
<p>With LangChain4j, the base approach is as follows.
Let&rsquo;s start with the <strong>ingestion phase</strong>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Load the document (the Wikipedia page about Berlin)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Document<span style="color:#bbb"> </span>capitalDocument<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Document.<span style="color:#4070a0">from</span>(text);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Define an embedding model to calculate vector embeddings,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// both for the text of the article, and for the user queries</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>embeddingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">endpoint</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_VERTEXAI_ENDPOINT&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;text-embedding-005&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Store the chunks and their vectors in a vector database</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// (in this example, we&#39;ll use a simple in-memory store)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>embeddingStore<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemoryEmbeddingStore<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Ingest the document in chunks of 100 characters</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// with an overlap of 20 characters,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// use the in-memory vector store,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// and the embedding model for vector calculations</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>EmbeddingStoreIngestor.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">documentSplitter</span>(DocumentSplitters.<span style="color:#4070a0">recursive</span>(100,<span style="color:#bbb"> </span>20))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">embeddingStore</span>(embeddingStore)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">embeddingModel</span>(embeddingModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">ingest</span>(capitalDocument);<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is the naive approach using chunks of 100 characters with overlap.
Let&rsquo;s see what it looks like during the <strong>retrieval phase</strong>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Declare the LLM model we want to use</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>chatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Create an interface contract</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// that LangChain4j will implement for us</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">CapitalsAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Result<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">learnAboutCapitals</span>(String<span style="color:#bbb"> </span>query);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// AiServices implements the interface</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// and binds the LLM, and a content retriever</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// that links the embedding model and vector store</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>CapitalsAssistant<span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiServices.<span style="color:#4070a0">builder</span>(CapitalsAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatLanguageModel</span>(chatModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">contentRetriever</span>(EmbeddingStoreContentRetriever.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">embeddingModel</span>(embeddingModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">embeddingStore</span>(embeddingStore)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Now we can ask questions</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Result<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>assistant.<span style="color:#4070a0">learnAboutCapitals</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;How many inhabitants live in Berlin?&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We could also add a memory component, to keep track of the ongoing discussion, it&rsquo;s just one extra line. But here, I stick to just single user questions.</p>
<h2 id="lets-implement-the-sentence-window-retrieval">Let&rsquo;s implement the sentence window retrieval</h2>
<p>Now, how can we expand the above code to implement the algorithm?</p>
<p>We need to split the text in sentences, and keep track of the surrounding sentences, as a sliding window, to give extra context to the LLM.
We can store that information as metadata of each text segment.
We must prepare the LLM prompt by inserting the surrounding context, instead of single sentences.</p>
<p>At ingestion phase, we can plug a <code>TextSegmentTransformer</code> that transforms our text chunks, to compute and store the surrounding context in the text segment metadata. We need to override both <code>transform()</code> and <code>transformAll()</code> methods, because we need to modify all chunks together (to get the surrounding sentences):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>EmbeddingStoreIngestor<span style="color:#bbb"> </span>ingestor<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>EmbeddingStoreIngestor.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>.<span style="color:#4070a0">documentSplitter</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>DocumentBySentenceSplitter(200,<span style="color:#bbb"> </span>20))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>.<span style="color:#4070a0">embeddingStore</span>(embeddingStore)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>.<span style="color:#4070a0">embeddingModel</span>(embeddingModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>.<span style="color:#4070a0">textSegmentTransformer</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextSegmentTransformer()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>TextSegment<span style="color:#bbb"> </span><span style="color:#06287e">transform</span>(TextSegment<span style="color:#bbb"> </span>segment)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>transformAll(Collections.<span style="color:#4070a0">singletonList</span>(segment))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">getFirst</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">transformAll</span>(List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>segments)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>list<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0;<span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">&lt;</span><span style="color:#bbb"> </span>segments.<span style="color:#4070a0">size</span>();<span style="color:#bbb"> </span>i<span style="color:#666">++</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextSegment<span style="color:#bbb"> </span>textSegment<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>segments.<span style="color:#4070a0">get</span>(i);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Create a sliding window of sentences to gather</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// the context surrounding the embedded sentence</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// (2 sentences before, 3 after,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// but you could make it configurable)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>context<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>IntStream.<span style="color:#4070a0">rangeClosed</span>(i<span style="color:#bbb"> </span><span style="color:#666">-</span><span style="color:#bbb"> </span>2,<span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">filter</span>(j<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>j<span style="color:#bbb"> </span><span style="color:#666">&gt;=</span><span style="color:#bbb"> </span>0<span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb"> </span>j<span style="color:#bbb"> </span><span style="color:#666">&lt;</span><span style="color:#bbb"> </span>segments.<span style="color:#4070a0">size</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">mapToObj</span>(j<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>segments.<span style="color:#4070a0">get</span>(j).<span style="color:#4070a0">text</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">collect</span>(Collectors.<span style="color:#4070a0">joining</span>(<span style="color:#4070a0">&#34; &#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// Store the surrounding context as metadata</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// of the text segment (the current chunk)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Metadata<span style="color:#bbb"> </span>metadata<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Metadata(textSegment.<span style="color:#4070a0">metadata</span>().<span style="color:#4070a0">toMap</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>metadata.<span style="color:#4070a0">put</span>(METADATA_CONTEXT_KEY,<span style="color:#bbb"> </span>context);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>list.<span style="color:#4070a0">add</span>(TextSegment.<span style="color:#4070a0">from</span>(textSegment.<span style="color:#4070a0">text</span>(),<span style="color:#bbb"> </span>metadata));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>list;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>That&rsquo;s a bit of code, but I hope to contribute an implementation to LangChain4j directly, so that you don&rsquo;t have to write this algorithm each time you want to apply it.</p>
<p>Let&rsquo;s focus now on the retrieval phase now, because we need to inject the surrounding context in the LLM prompt, instead of the sentence chunk itself. We need to create a <code>RetrievalAugmentor</code>, and configure the <code>ContentRetriever</code> we used before, and a <code>ContentInjector</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>CapitalsAssistant<span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>AiServices.<span style="color:#4070a0">builder</span>(CapitalsAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatLanguageModel</span>(chatModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">retrievalAugmentor</span>(DefaultRetrievalAugmentor.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// the content retriever is defined</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// at the level of the retrieval augmentor</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">contentRetriever</span>(EmbeddingStoreContentRetriever.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">embeddingModel</span>(embeddingModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">embeddingStore</span>(embeddingStore)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// We create a content injector that injects</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// the surrounding context in the LLM prompt</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">contentInjector</span>((contents,<span style="color:#bbb"> </span>userMessage)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#60a0b0;font-style:italic">// Retrieves the surrounding sentences</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#60a0b0;font-style:italic">// from the text segment&#39;s metadata</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>String<span style="color:#bbb"> </span>excerpts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>contents.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">map</span>(content<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>content<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">textSegment</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">metadata</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">getString</span>(METADATA_CONTEXT_KEY))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">collect</span>(Collectors.<span style="color:#4070a0">joining</span>(<span style="color:#4070a0">&#34;\n\n&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#60a0b0;font-style:italic">// Customize the prompt for our geography use case</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>PromptTemplate.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a helpful geography assistant
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        knowing everything about the capitals of the world.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Here&#39;s the question from the user:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &lt;question&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {{userMessage}}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &lt;/question&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Answer the question using the following information:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &lt;excerpts&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {{contents}}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &lt;/excerpts&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>).<span style="color:#4070a0">apply</span>(Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#4070a0">&#34;userMessage&#34;</span>,<span style="color:#bbb"> </span>userMessage.<span style="color:#4070a0">singleText</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#4070a0">&#34;contents&#34;</span>,<span style="color:#bbb"> </span>excerpts<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>)).<span style="color:#4070a0">toUserMessage</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>})).<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Again, that&rsquo;s a bit of code, but we can make it reusable easily if needed in different contexts.</p>
<h2 id="summary">Summary</h2>
<p>With this <em>sentence window retrieval</em> approach, we calculate and store the vector embedding of a sentence, but we inject a wider surrounding context (a few sentences before and after) into the context of the LLM to generate its response with more information than just the single sentence. This tends to avoid the problem of key pieces of information cut in the middle, and to resolve references between sentences (like a pronoun pointing at a named entity defined earlier).</p>
<p>It&rsquo;s a technique worth experimenting with, to see if it gives better results in your own scenario.
However, before blindly applying a particular technique, be sure to prepare some evaluations: Measure the quality of your RAG pipeline before making changes. Then, measure after having applied a new technique, to see if the answers are better.</p>
<p>We&rsquo;ll have to explore the topic of evaluation another day, but in the meantime, I encourage you to read the blog posts of my colleague Mete Atamel who covered <a href="https://atamel.dev/posts/2025/01-09_evaluating_rag_pipelines/">RAG pipeline evaluation</a>, the <a href="https://atamel.dev/posts/2025/01-14_rag_evaluation_deepeval/">DeepEval</a> tool, and the <a href="https://atamel.dev/posts/2025/01-21_improve-rag-with-rag-triad-metrics/">RAG triad metric</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The power of large context windows for your documentation efforts</title><link>https://glaforge.dev/posts/2025/02/15/the-power-of-large-context-windows-for-your-documentation-efforts/</link><pubDate>Sat, 15 Feb 2025 13:55:41 +0100</pubDate><guid>https://glaforge.dev/posts/2025/02/15/the-power-of-large-context-windows-for-your-documentation-efforts/</guid><description>&lt;p>My colleague Jaana Dogan was &lt;a href="https://x.com/rakyll/status/">pointing&lt;/a> at the Anthropic&amp;rsquo;s MCP (Model Context Protocol) documentation pages which were describing &lt;a href="https://modelcontextprotocol.io/tutorials/building-mcp-with-llms">how to build MCP servers and clients&lt;/a>.
The interesting twist was about &lt;em>preparing the documentation&lt;/em> in order to have Claude &lt;em>assist you&lt;/em> in building those MCP servers &amp;amp; clients, &lt;em>rather than clearly documenting how to do so&lt;/em>.&lt;/p>
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">MCP tutorials are great. There are no tutorials really. &lt;br>&lt;br>&amp;quot;Copy these resources to Claude, and start asking some questions like...&amp;quot; &lt;a href="https://t.co/GG50DMWNLW">pic.twitter.com/GG50DMWNLW&lt;/a>&lt;/p></description><content:encoded>
<![CDATA[<p>My colleague Jaana Dogan was <a href="https://x.com/rakyll/status/">pointing</a> at the Anthropic&rsquo;s MCP (Model Context Protocol) documentation pages which were describing <a href="https://modelcontextprotocol.io/tutorials/building-mcp-with-llms">how to build MCP servers and clients</a>.
The interesting twist was about <em>preparing the documentation</em> in order to have Claude <em>assist you</em> in building those MCP servers &amp; clients, <em>rather than clearly documenting how to do so</em>.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">MCP tutorials are great. There are no tutorials really. <br><br>&quot;Copy these resources to Claude, and start asking some questions like...&quot; <a href="https://t.co/GG50DMWNLW">pic.twitter.com/GG50DMWNLW</a></p>&mdash; Jaana Dogan ヤナ ドガン (@rakyll) <a href="https://twitter.com/rakyll/status/1890521760690270409?ref_src=twsrc%5Etfw">February 14, 2025</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>No more tutorials:</p>
<ul>
<li><strong>You discuss with the reference documentation.</strong></li>
<li><strong>Your chat with the LLM becomes the tutorial!</strong></li>
</ul>
<p>LLM-powered documentation chats become your tailored tutorial, for your very own specific need and requirements.
Not only LLMs can assist you authoring articles, documentation, reports, but <strong>LLMs can craft explanations that help you achieve a particular goal for which there&rsquo;s not already a tutorial or how-to guide available</strong>.</p>
<p>Also, sometimes, you overlook some key paragraph of section when browsing through the documentation, and you miss the key information that would have helped you fix the problem at hand. This happened to me recently while using an Obsidian plugin: I needed to configure the plugin in a certain way, and I had the impression it wasn&rsquo;t possible, but the plugin author pointed me at the key paragraph that I somehow glanced over. <strong>Had I asked the question to an LLM-powered chat that has all the reference documentation in its context, maybe it would have found that paragraph I missed?</strong></p>
<h2 id="here-come-the-large-context-window-llms">Here come the large context window LLMs&hellip;</h2>
<p>As you may already know, I contribute to the <a href="https://docs.langchain4j.dev/">LangChain4j</a> open source project, which provides integrations with various LLMs (like Gemini) or vector databases. I hope to start working on an additional module to integrate the new <em>unified</em> <a href="https://github.com/googleapis/java-genai">Gemini SDK</a>.
The advantage of this new SDK is that you can call both Gemini <em>flavors</em>: the one provided by Google Cloud&rsquo;s Vertex AI offering, as well as DeepMind&rsquo;s Google AI version.
One SDK to rule them all!</p>
<p>I&rsquo;m also interested in potentially creating a new vector store module for Google <a href="https://cloud.google.com/firestore/docs/vector-search">Cloud Firestore</a>, which recently added vector calculation support in its Java client library. It would be neat to be able to use Firestore for RAG (Retrieval Augmented Generation) scenarios, taking advantage of the document database, its filtering capabilities, and its ability to do vector similarity searches.</p>
<p>LangChain4j&rsquo;s documentation provides some explanations on how to contribute new integrations, but I was interested in trying this approach of chatting with the reference documentation to guide my steps in creating a new LLM module, and a new vector store implementation module.</p>
<p>Fortunately, my favorite large language model, <a href="https://gemini.google.com/app">Gemini</a>, has a <strong>huge context window</strong> of <a href="https://developers.googleblog.com/en/gemini-2-family-expands/">up to 2 million tokens</a>!
This should be enough to ingurgitate the whole sources and documentation pages of the project.</p>
<p>Additionally, I discovered <code>gitingest</code> a great <a href="https://gitingest.com/">online tool</a> that allows you to <strong>convert a Github project and all its sources into one gigantic text file</strong>&hellip; that you can then feed to an LLM. Basically, you take the URL of a Github repository, and you replace the <code>github.com</code> part with <code>gitingest.com</code>. For example, for LangChain4j, the URL becomes: <code>https://gitingest.com/langchain4j/langchain4j</code>. Then, a download button lets you get the whole content of the repository.</p>
<h2 id="the-prompt">The prompt</h2>
<p>For this experiment, I didn&rsquo;t code anything, I simply used <a href="https://aistudio.google.com/app/prompts/new_chat">Google AI Studio</a>.</p>
<p>I selected <code>Gemini 2.0 Pro experimental</code>:</p>
<p><figure>
  <a href="#img-16e7f8b6b2de31376d0d2b387f16d9ce">
    <img src="/img/gemini/gemini-2.0-pro-exp.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-16e7f8b6b2de31376d0d2b387f16d9ce">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/gemini-2.0-pro-exp.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then, I loaded the big text file of the LangChain4j project, created by gitingest, and look at the number of tokens:</p>
<p><figure>
  <a href="#img-8584e180376ac4745f717292d8c34c34">
    <img src="/img/gemini/2-million-tokens.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8584e180376ac4745f717292d8c34c34">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/2-million-tokens.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Yes, the whole repository and documentation is almost 2 million tokens long!
Only Gemini can ingest that many tokens!</p>
<p>Time to ask my question:</p>
<blockquote>
<p>I&rsquo;d like to create a new language module for LangChain4j. How should I do that? What are the key steps to follow to propose a new model?</p></blockquote>
<h2 id="the-response">The response</h2>

<details>
  <summary>Click to reveal the detailed answer</summary>
  <p>Okay, let&rsquo;s outline the steps to create a new language model module for LangChain4j and propose its inclusion. Based on the provided file structure, you&rsquo;ll be focusing on creating a new module similar to the existing ones (e.g., <code>langchain4j-open-ai</code>, <code>langchain4j-ollama</code>, etc.). Here&rsquo;s a breakdown of the process, referencing the structure you&rsquo;ve provided:</p>
<p><strong>Key Steps and Considerations</strong></p>
<ol>
<li>
<p><strong>Understand the Abstractions and SPI:</strong> LangChain4j, like its Python counterpart, is built around core abstractions. You need to understand these to implement your integration correctly. The core abstractions you <em>must</em> implement are:</p>
<ul>
<li><strong><code>ChatLanguageModel</code> / <code>StreamingChatLanguageModel</code>:</strong> For conversational models (like ChatGPT, Gemini). Implement <code>ChatLanguageModel</code> for synchronous responses, and <code>StreamingChatLanguageModel</code> if the model supports streaming responses token by token.</li>
<li><strong><code>LanguageModel</code> / <code>StreamingLanguageModel</code>:</strong> For models with a simpler text-in, text-out interface (less common these days).</li>
<li><strong><code>EmbeddingModel</code>:</strong> If the model provider offers embedding capabilities.</li>
<li><strong><code>ModerationModel</code>:</strong> If the model provider offers content moderation.</li>
<li><strong><code>ScoringModel</code>:</strong> If the model provider offers scoring/ranking capabilities.</li>
<li><strong>Builder Factories:</strong> You&rsquo;ll also need to create builder factories (SPIs) for each model type you implement. These are how users will construct your model classes. See examples like <code>AzureOpenAiChatModelBuilderFactory</code>. These are registered using the Java ServiceLoader mechanism (the <code>META-INF/services</code> files).</li>
</ul>
</li>
<li>
<p><strong>Choose a Module Structure (and Repository):</strong></p>
<ul>
<li><strong>Community Repo (Preferred for new integrations):</strong> Start your integration in the <a href="https://github.com/langchain4j/langchain4j-community"><code>langchain4j-community</code></a> repository. This is the recommended approach for new contributions. It allows for easier initial review and iteration before considering a move to the core <code>langchain4j</code> repository. Clone this repo, don&rsquo;t fork the main <code>langchain4j</code> repo directly.</li>
<li><strong>Main <code>langchain4j</code> Repo (For Core Integrations):</strong> If your integration is with a very widely used and well-established model provider (like OpenAI, Google, etc.), <em>and</em> you are confident in its stability and long-term maintenance, you <em>might</em> propose it for the main repo. However, start in <code>langchain4j-community</code> first.</li>
<li><strong>Module Naming:</strong> Follow the pattern: <code>langchain4j-{provider-name}</code> (e.g., <code>langchain4j-my-llm</code>).</li>
<li><strong>Directory Structure:</strong> Create a directory structure mirroring the existing modules (see <code>langchain4j-open-ai</code> or <code>langchain4j-ollama</code> as good examples):
<pre tabindex="0"><code>langchain4j-{provider-name}/
    pom.xml  (Your module&#39;s Maven build file)
    src/
        main/
            java/
                dev/
                    langchain4j/
                        model/
                            {providername}/  (e.g., myllm)
                                {ProviderName}ChatModel.java  (Your implementation)
                                internal/ (API client and related classes)
                                spi/      (Builder factory for your model)
                                    {ProviderName}ChatModelBuilderFactory.java
            resources/
                META-INF/
                    services/
                        (Files to register your builder factory, see examples)
        test/
            java/
                dev/
                    langchain4j/
                        model/
                            {providername}/
                                {ProviderName}ChatModelIT.java (Integration tests)
</code></pre></li>
</ul>
</li>
<li>
<p><strong>Implement the API Client:</strong></p>
<ul>
<li><strong>Official SDK (Preferred):</strong> If the LLM provider has an official Java SDK, use it. This is usually the best approach for stability, performance, and access to all features. See <code>langchain4j-bedrock</code> for an example using an official SDK.</li>
<li><strong>HTTP Client (If no SDK):</strong> If there&rsquo;s no official SDK, use the JDK&rsquo;s built-in <code>java.net.http.HttpClient</code> (available since Java 11). This minimizes external dependencies. Avoid adding new dependencies unless absolutely necessary. See <code>http-clients/langchain4j-http-client-jdk</code> for how LangChain4j wraps this. Avoid using the older <code>okhttp3</code> directly if possible, prefer <code>langchain4j-http-client-jdk</code> (or <code>langchain4j-http-client-spring-restclient</code> if building a Spring Boot starter).</li>
<li><strong>JSON Handling:</strong> Use Jackson for JSON serialization/deserialization, as it&rsquo;s already a dependency.</li>
<li><strong>Error Handling:</strong> Make sure to handle HTTP errors (non-2xx responses) appropriately. Throw a <code>dev.langchain4j.exception.HttpException</code> for these.</li>
<li><strong>Request/Response Logging:</strong> Implement logging for requests and responses (see <code>langchain4j-anthropic</code> for a complete example). This is <em>very</em> helpful for debugging.</li>
</ul>
</li>
<li>
<p><strong>Implement the Model Interface(s):</strong></p>
<ul>
<li>Implement <code>ChatLanguageModel</code>, <code>StreamingChatLanguageModel</code>, <code>EmbeddingModel</code>, etc., as appropriate, based on the provider&rsquo;s capabilities.</li>
<li>Use the <code>Builder</code> pattern for your model classes to allow for flexible configuration.</li>
<li>Make sure your implementation handles request/response mapping and error handling correctly.</li>
<li>Implement <code>TokenCountEstimator</code> if possible, so the <code>TokenWindowChatMemory</code> can calculate the token usage.
Implement <code>DimensionAwareEmbeddingModel</code> to report the output dimension from the embedding model.</li>
</ul>
</li>
<li>
<p><strong>Write Tests:</strong></p>
<ul>
<li><strong>Unit Tests:</strong> Create unit tests for any complex logic, utility methods, and request/response mappers.</li>
<li><strong>Integration Tests (ITs):</strong> Create integration tests (e.g., <code>MyLlmChatModelIT.java</code>) that interact with the <em>real</em> LLM provider&rsquo;s API. These are <em>crucial</em> for ensuring your integration works correctly.
<ul>
<li>Use environment variables (e.g., <code>MYLLM_API_KEY</code>) to store API keys and other secrets. Do <em>not</em> hardcode them.</li>
<li>Use <code>@EnabledIfEnvironmentVariable</code> to skip the tests if the required environment variables are not set.</li>
<li>Extend <code>AbstractChatModelIT</code>, <code>AbstractStreamingChatModelIT</code>, <code>AbstractEmbeddingModelIT</code>, and/or <code>AbstractScoringModelIT</code> to get a set of basic tests.</li>
<li>Test all relevant features of the model (e.g., text generation, streaming, different parameters, tool use, JSON mode).</li>
<li>Add test for concurrent requests if possible.</li>
<li>Consider adding a test for the <code>Tokenizer</code> interface (see examples in <code>langchain4j-core</code>).</li>
<li>Add <code>@RetryingTest</code> if model response is inconsistent</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Add to BOM (Bill of Materials):</strong> Add your new module to <code>langchain4j-bom/pom.xml</code>. This helps users manage dependencies.</p>
</li>
<li>
<p><strong>Documentation:</strong></p>
<ul>
<li><strong>Update <code>README.md</code>:</strong> Add your integration to the list of supported models and embedding stores.</li>
<li><strong>Create Markdown Documentation:</strong> Create Markdown files in the <code>docs/docs/integrations/</code> directory, following the structure of existing integrations. You&rsquo;ll need:
<ul>
<li>A main file (e.g., <code>my-llm.md</code>).</li>
<li>An entry in <code>docs/docs/integrations/language-models/index.md</code> and in <code>docs/sidebars.js</code>.</li>
<li>An entry in <code>_category_.json</code> files in <code>docs/docs/integrations/language-models</code> and <code>docs/docs/integrations/embedding-stores</code></li>
</ul>
</li>
<li><strong>Examples (Highly Recommended):</strong> Create a simple example in the <a href="https://github.com/langchain4j/langchain4j-examples"><code>langchain4j-examples</code></a> repository. This is <em>very</em> helpful for users.</li>
</ul>
</li>
<li>
<p><strong>General Guidelines (from CONTRIBUTING.md):</strong></p>
<ul>
<li><strong>Java 17:</strong> Maintain compatibility with Java 17.</li>
<li><strong>Minimal Dependencies:</strong> Avoid adding new dependencies if possible. If necessary, try to use libraries already present. Run <code>mvn dependency:analyze</code> to check.</li>
<li><strong>Backwards Compatibility:</strong> Avoid breaking changes. If necessary, deprecate old methods/fields instead of removing them.</li>
<li><strong>Naming Conventions:</strong> Follow existing naming conventions.</li>
<li><strong>No Lombok:</strong> Avoid using Lombok in new code; remove it from existing code if you touch it.</li>
<li><strong>Javadoc:</strong> Add Javadoc where needed.</li>
<li><strong>Code Style:</strong> Run <code>make lint</code> and <code>make format</code> before committing.</li>
<li><strong>Large Features:</strong> Discuss large features with maintainers (@langchain4j) before implementation.</li>
</ul>
</li>
<li>
<p><strong>Open a Pull Request (Draft First):</strong></p>
<ul>
<li>Open a draft PR in the <code>langchain4j-community</code> repository.</li>
<li>Fill out all sections of the PR template.</li>
<li>Once the PR is reviewed and approved, you will be asked to finalize it (add documentation, examples, etc.).</li>
</ul>
</li>
</ol>
<p><strong>Example Code Snippets (Illustrative)</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// MyLlmChatModel.java (in the appropriate package)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MyLlmChatModel</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>ChatLanguageModel<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>MyLlmClient<span style="color:#bbb"> </span>client;<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Your custom client</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Builder</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#06287e">MyLlmChatModel</span>(String<span style="color:#bbb"> </span>baseUrl,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>apiKey,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>modelName,<span style="color:#bbb"> </span>Duration<span style="color:#bbb"> </span>timeout)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">client</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>MyLlmClient(baseUrl,<span style="color:#bbb"> </span>apiKey,<span style="color:#bbb"> </span>timeout);<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// Your custom client</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">modelName</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">generate</span>(List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>messages)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 1. Convert Langchain4j messages to MyLLM&#39;s request format</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 2. Call client.chat(...)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 3. Convert MyLLM&#39;s response to LangChain4j&#39;s Response&lt;AiMessage&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 4. Handle errors (throw HttpException)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>;<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// TODO: Implement</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... other methods, builder, etc. ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// MyLlmClient.java (in a suitable package, e.g., dev.langchain4j.model.myllm.internal)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MyLlmClient</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... implementation using java.net.http.HttpClient ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// MyLlmChatModelBuilderFactory.java (in a suitable package, e.g., dev.langchain4j.model.myllm.spi)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">MyLlmChatModelBuilderFactory</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>Supplier<span style="color:#666">&lt;</span>MyLlmChatModel.<span style="color:#4070a0">Builder</span><span style="color:#666">&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>MyLlmChatModel.<span style="color:#4070a0">Builder</span><span style="color:#bbb"> </span><span style="color:#06287e">get</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>MyLlmChatModel.<span style="color:#4070a0">builder</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// META-INF/services/dev.langchain4j.model.chat.spi.ChatLanguageModelBuilderFactory (resource file)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Add a line with the fully qualified name of your factory:</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// dev.langchain4j.model.myllm.spi.MyLlmChatModelBuilderFactory</span><span style="color:#bbb">
</span></span></span></code></pre></div><p><strong>Key Points and Common Mistakes</strong></p>
<ul>
<li><strong>Thorough Testing:</strong> Integration tests are absolutely essential. Test with real API calls.</li>
<li><strong>Error Handling:</strong> Handle HTTP errors (non-2xx responses) from the LLM provider&rsquo;s API.</li>
<li><strong>Token Usage:</strong> If the LLM provider gives you token usage information, include it in the <code>Response</code>.</li>
<li><strong>Finish Reason:</strong> If the LLM provider gives you a finish reason (e.g., &ldquo;stop&rdquo;, &ldquo;length&rdquo;), include it in the <code>Response</code>.</li>
<li><strong>Consistency:</strong> Maintain consistency with existing LangChain4j modules in terms of naming, structure, and coding style.</li>
<li><strong>Documentation:</strong> Clear, concise, and accurate documentation is critical. Follow the patterns you see in existing modules.</li>
<li><strong>SPI Registration:</strong> Remember to create the <code>src/main/resources/META-INF/services/</code> files to register your model implementation as a service.</li>
</ul>
<p>This comprehensive guide, combined with the provided file structure and examples, should give you a strong foundation for contributing your new language model integration to LangChain4j. Remember to start in the <code>langchain4j-community</code> repository for initial development and review. Good luck!</p>
</details>

<h2 id="the-verdict">The verdict</h2>
<p>Honestly, this is really good! It follows the advice in the <a href="https://github.com/langchain4j/langchain4j/blob/main/CONTRIBUTING.md">contributing guidelines</a>.
But since it has access to the whole code base, it understands pretty well the various steps to follow, the classes to implement, etc.</p>
<p>A few nits though: for example, it confirms we shouldn&rsquo;t use Lombok for creating builders, but the example code shows a <code>@Builder</code> annotation from Lombok, because it&rsquo;s still used in several modules. Or there&rsquo;s this reference to the <code>make</code> tool, although the project doesn&rsquo;t use it at all. The code suggests implement the <code>generate()</code> methods, but newer models should now implement the new <code>chat()</code> method instead, but Gemini can&rsquo;t really know about this, as this is fairly new and not many models implement those interfaces for now.</p>
<p>But other than that, it&rsquo;s excellent!</p>
<p>Gemini created a contributing guide by looking at almost 2 million tokens of code and documentation.
But this doesn&rsquo;t end here, you can continue asking other questions, to get more insight into particular aspects of the development of a new module, and Gemini would continue the discussion, guiding you and helping you in your task. I found that quite impressive.</p>
<h2 id="lets-continue-the-conversation-with-the-code-base-and-documentation">Let&rsquo;s continue the conversation with the code base and documentation</h2>
<p>As I was interested in creating a new embedding store for Firestore, I then added in the context the <a href="https://cloud.google.com/firestore/docs/vector-search">documentation</a> of the Java SDK for Firestore. And asked the following question:</p>
<blockquote>
<p>I would also like to create a new embedding store for the Google Cloud Firestore database.
What are the steps to create a new LangChain4j embedding store module?</p></blockquote>
<p>Let&rsquo;s see what it suggested:</p>

<details>
  <summary>Click to reveal the detailed answer</summary>
  <p>Okay, here&rsquo;s a breakdown of how to create a new LangChain4j embedding store module for Google Cloud Firestore, along with the key steps and considerations, mirroring the structure of existing modules like <code>langchain4j-milvus</code>.</p>
<p><strong>Project Structure</strong></p>
<p>Your project structure should follow the established pattern. I&rsquo;ll create a simplified version based on the most relevant parts from the provided file listing. The full structure would be much larger (like the main <code>langchain4j</code> project), but this captures the essentials:</p>
<pre tabindex="0"><code>langchain4j/
└── langchain4j-embedding-store-google-firestore/  (or similar name)
    ├── pom.xml                                     (Your module&#39;s Maven build file)
    └── src/
        └── main/
            └── java/
                └── dev/
                    └── langchain4j/
                        └── store/
                            └── embedding/
                                └── google/
                                    └── firestore/
                                        └── GoogleFirestoreEmbeddingStore.java  (Main class)
                                        └── (Any other supporting classes, like request/response objects)
        └── test/
            └── java/
                └── dev/
                    └── langchain4j/
                        └── store/
                            └── embedding/
                                └── google/
                                    └── firestore/
                                        └── GoogleFirestoreEmbeddingStoreIT.java (Integration test)
</code></pre><p><strong>Steps to Create the Module</strong></p>
<ol>
<li>
<p><strong>Project Setup (Maven Module):</strong></p>
<ul>
<li>Create a new directory named <code>langchain4j-embedding-store-google-firestore</code> (or a similar, descriptive name) within the <code>langchain4j</code> parent directory.</li>
<li>Create a <code>pom.xml</code> file inside this new directory. This file will define your module as a Maven project and specify its dependencies. Use <code>langchain4j-milvus/pom.xml</code> as a guide, but adapt it:
<ul>
<li><strong>Parent:</strong> Set the parent to <code>langchain4j-parent</code>, as shown in your example.</li>
<li><strong>ArtifactId:</strong> Use <code>langchain4j-embedding-store-google-firestore</code>.</li>
<li><strong>Name and Description:</strong> Update these appropriately.</li>
<li><strong>Dependencies:</strong> Include <code>langchain4j-core</code> as a compile-time dependency. Add the Google Cloud Firestore Java client library as a dependency:
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-cloud-firestore<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>YOUR_VERSION_HERE<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span> <span style="color:#60a0b0;font-style:italic">&lt;!-- Lookup the latest version --&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div>Add any other necessary dependencies, such as logging (SLF4J), Lombok (if desired, but try to avoid in new code), etc. <em>Minimize dependencies as much as possible.</em></li>
<li><strong>Test Dependencies:</strong> Include JUnit, AssertJ, and any mocking libraries (like Mockito) in the <code>test</code> scope.</li>
<li><strong>Licenses:</strong> Ensure your module has the correct Apache 2.0 license information in the POM.</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Implement <code>EmbeddingStore&lt;TextSegment&gt;</code>:</strong></p>
<ul>
<li>Create <code>GoogleFirestoreEmbeddingStore.java</code> in the <code>dev.langchain4j.store.embedding.google.firestore</code> package.</li>
<li>Implement the <code>EmbeddingStore&lt;TextSegment&gt;</code> interface from <code>langchain4j-core</code>. This is the <em>crucial</em> part. You&rsquo;ll need to implement the following methods, mapping them to Firestore operations:
<ul>
<li><code>add(Embedding embedding)</code>: Adds a single embedding to the store, generating a unique ID.</li>
<li><code>add(String id, Embedding embedding)</code>: Adds an embedding with a specified ID.</li>
<li><code>add(Embedding embedding, TextSegment textSegment)</code>: Adds an embedding with associated text and metadata.</li>
<li><code>addAll(List&lt;Embedding&gt; embeddings)</code>: Adds multiple embeddings.</li>
<li><code>addAll(List&lt;String&gt; ids, List&lt;Embedding&gt; embeddings, List&lt;TextSegment&gt; textSegments)</code>: Adds multiple embeddings with associated IDs and text segments.</li>
<li><code>removeAll(Collection&lt;String&gt; ids)</code>: Delete embeddings by id</li>
<li><code>removeAll()</code>: Deletes everything</li>
<li><code>removeAll(Filter filter)</code>: Deletes records using the condition filter</li>
<li><code>search(EmbeddingSearchRequest request)</code>: Find related embeddings</li>
<li><code>findRelevant(Embedding referenceEmbedding, int maxResults, double minScore)</code>: Finds relevant embeddings.</li>
</ul>
</li>
<li><strong>Considerations for Firestore Implementation:</strong>
<ul>
<li><strong>Data Model:</strong> How will you store the embeddings (as float arrays) and associated data (text, metadata)? Firestore uses a NoSQL document model. You&rsquo;ll likely store each <code>TextSegment</code> and its <code>Embedding</code> as a document in a collection.</li>
<li><strong>Metadata:</strong> Firestore supports storing metadata as document fields. You&rsquo;ll need a way to map <code>TextSegment</code> metadata (which is a <code>Map&lt;String, String&gt;</code>) to Firestore document fields. You have a few options here, mirroring what existing modules do:
<ul>
<li><strong>Individual Columns (Preferred):</strong> Each metadata key becomes a separate field in the document. This is efficient for querying but requires knowing the metadata keys in advance. The Mariadb integration uses this approach with <code>MetadataColumDefinition</code>.</li>
<li><strong>Single JSON Field:</strong> Store all metadata as a single JSON string. This is flexible but less efficient for filtering. The Chroma integration uses this.</li>
<li><strong>Mixed Approach:</strong> Common metadata fields (like &ldquo;source&rdquo; or &ldquo;document_id&rdquo;) could be separate fields, and a catch-all &ldquo;metadata&rdquo; field could store the rest as JSON.</li>
</ul>
</li>
<li><strong>Vector Search:</strong> Firestore now has native Vector Search, which is excellent! You&rsquo;ll use this for the <code>findRelevant</code> method. The key will be understanding how to map LangChain4j&rsquo;s <code>EmbeddingSearchRequest</code> (which includes <code>maxResults</code> and <code>minScore</code>) to the appropriate Firestore vector search query.</li>
<li><strong>Filtering:</strong> Implement filtering based on metadata using Firestore&rsquo;s query capabilities. LangChain4j&rsquo;s <code>Filter</code> interface needs to be translated into a Firestore query. Look at existing implementations like <code>ChromaMetadataFilterMapper</code> for inspiration, but adapt it to Firestore&rsquo;s query syntax.</li>
<li><strong>Error Handling:</strong> Wrap Firestore exceptions in <code>RuntimeException</code> or a more specific custom exception.</li>
<li><strong>Configuration:</strong> Use a <code>Builder</code> pattern (like <code>OpenAiChatModel</code> does) to allow users to configure:
<ul>
<li>Firestore project ID, database, collection name</li>
<li>Credentials (API key, service account)</li>
<li>Timeout settings</li>
<li>Maximum number of results (<code>maxResults</code>)</li>
<li>Minimum relevance score (<code>minScore</code>)</li>
<li>Field names for text, embedding, and metadata (if configurable)</li>
<li>Possibly options for automatically creating the collection/indexes.</li>
</ul>
</li>
<li><strong>Indexing:</strong> You <em>must</em> create the necessary index in Firestore for vector search to work. The documentation you provided explains how to do this with the <code>gcloud</code> CLI. The code should, ideally, at least <em>check</em> if the index exists. It <em>could</em> even attempt to create it, but this would require more permissions.</li>
<li><strong>Concurrency:</strong> if there is any concurrency, the code should be thread-safe.</li>
</ul>
</li>
</ul>
</li>
</ol>
<ul>
<li><strong>Implement <code>GoogleFirestoreEmbeddingModel</code>:</strong> You should also implement the <code>EmbeddingModel</code> interface, by adapting the google API to the langchain4j interface.</li>
</ul>
<ol start="3">
<li>
<p><strong>Create SPI Builder Factory:</strong></p>
<ul>
<li>Create a <code>GoogleFirestoreEmbeddingStoreBuilderFactory</code> class that implements <code>Supplier&lt;GoogleFirestoreEmbeddingStore.Builder&gt;</code>.</li>
<li>Create a file named <code>META-INF/services/dev.langchain4j.spi.store.embedding.EmbeddingStoreFactory</code> in <code>src/main/resources</code>.</li>
<li>Add a single line to this file containing the fully qualified name of your factory class (e.g., <code>dev.langchain4j.store.embedding.google.firestore.GoogleFirestoreEmbeddingStoreBuilderFactory</code>).</li>
</ul>
</li>
<li>
<p><strong>Write Integration Tests:</strong></p>
<ul>
<li>Create <code>GoogleFirestoreEmbeddingStoreIT.java</code> (and potentially <code>...WithFilteringIT.java</code>, <code>...WithRemovalIT.java</code>, etc., mirroring the existing modules).</li>
<li>Extend <code>EmbeddingStoreIT</code> (or <code>EmbeddingStoreWithFilteringIT</code>, etc.) to inherit a basic set of tests.</li>
<li>Implement the abstract methods (like <code>embeddingStore()</code>) to provide instances of your store and a compatible embedding model.</li>
<li>Add tests specific to Google Cloud Firestore features and limitations.</li>
<li>Use <code>@EnabledIfEnvironmentVariable</code> to conditionally run the tests only when the necessary environment variables (credentials) are set. See <code>OllamaChatModelIT</code> for an example.</li>
</ul>
</li>
<li>
<p><strong>Add to BOM (Bill of Materials):</strong> Add your new module to <code>langchain4j-bom/pom.xml</code> to manage its version consistently.</p>
</li>
<li>
<p><strong>Documentation:</strong></p>
<ul>
<li>Add the new model in all the <code>index.md</code> files</li>
<li>Write a guide in <code>docs/docs/integrations/embedding-stores</code></li>
<li>Create a README file</li>
</ul>
</li>
</ol>
<p><strong>Code Example (Conceptual)</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// In your GoogleFirestoreEmbeddingStore.java</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.store.embedding.google.firestore</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.data.embedding.Embedding</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.data.segment.TextSegment</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.embedding.EmbeddingModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.store.embedding.EmbeddingMatch</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.store.embedding.EmbeddingStore</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">lombok.Builder</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.slf4j.Logger</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.slf4j.LoggerFactory</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.List</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// ... other imports ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GoogleFirestoreEmbeddingStore</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>EmbeddingStore<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>Logger<span style="color:#bbb"> </span>log<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>LoggerFactory.<span style="color:#4070a0">getLogger</span>(GoogleFirestoreEmbeddingStore.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... (fields for Firestore client, collection name, etc.) ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Builder</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#06287e">GoogleFirestoreEmbeddingStore</span>(...)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... (initialize Firestore client, etc.) ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">add</span>(Embedding<span style="color:#bbb"> </span>embedding)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... implementation ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">add</span>(String<span style="color:#bbb"> </span>id,<span style="color:#bbb"> </span>Embedding<span style="color:#bbb"> </span>embedding)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... implementation ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">add</span>(Embedding<span style="color:#bbb"> </span>embedding,<span style="color:#bbb"> </span>TextSegment<span style="color:#bbb"> </span>textSegment)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... implementation ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">addAll</span>(List<span style="color:#666">&lt;</span>Embedding<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>embeddings)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... implementation ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">addAll</span>(List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>ids,<span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>Embedding<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>embeddings,<span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>embedded)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... implementation ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>EmbeddingMatch<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">findRelevant</span>(Embedding<span style="color:#bbb"> </span>referenceEmbedding,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>maxResults,<span style="color:#bbb"> </span><span style="color:#902000">double</span><span style="color:#bbb"> </span>minScore)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... implementation using Firestore vector search ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... map Firestore results to EmbeddingMatch objects ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... other methods ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// In a separate file: GoogleFirestoreEmbeddingStoreBuilderFactory.java</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.store.embedding.google.firestore</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.embedding.EmbeddingModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.spi.store.embedding.EmbeddingStoreFactory</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GoogleFirestoreEmbeddingStoreBuilderFactory</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>EmbeddingStoreFactory<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>GoogleFirestoreEmbeddingStore.<span style="color:#4070a0">Builder</span><span style="color:#bbb"> </span><span style="color:#06287e">get</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">       </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>GoogleFirestoreEmbeddingStore.<span style="color:#4070a0">builder</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p><strong>Key Improvements and Considerations from the examples:</strong></p>
<ul>
<li><strong>Error Handling:</strong> Wrap Firestore API calls in <code>try-catch</code> blocks and throw <code>RuntimeException</code> (or a more specific custom exception) to handle potential errors.</li>
<li><strong>Logging:</strong> Use SLF4J (<code>log.debug()</code>, <code>log.info()</code>, <code>log.warn()</code>, <code>log.error()</code>) for logging. This is crucial for debugging and monitoring.</li>
<li><strong>Configuration:</strong> The <code>Builder</code> should allow configuration of all necessary parameters (endpoint, credentials, collection name, timeouts, etc.). Use <code>ensureNotNull</code> and <code>ensureNotBlank</code> to validate.</li>
<li><strong>Dependency Management:</strong> The <code>pom.xml</code> should correctly specify the Google Cloud Firestore client library as a dependency.</li>
<li><strong>Testcontainers:</strong> Consider using Testcontainers for integration tests to spin up a Firestore emulator. This will make your tests more reliable and portable. (See examples in the provided code, such as for <code>CassandraEmbeddingStoreDockerIT</code>).</li>
<li><strong>Metadata:</strong> Implement proper handling of metadata, including mapping it to/from Firestore document fields. Decide on your strategy (separate fields vs. JSON).</li>
<li><strong>Filtering:</strong> Implement filtering based on metadata using Firestore&rsquo;s query capabilities. You will likely need a helper class like <code>ChromaMetadataFilterMapper</code> to translate <code>Filter</code> objects into Firestore queries.</li>
<li><strong>Service Provider Interface (SPI):</strong> The <code>spi</code> package and <code>META-INF/services</code> file are <em>critical</em> for LangChain4j to discover your implementation. This is how <code>AiServices</code> (and other parts of the framework) dynamically load components. Without this, your module won&rsquo;t be usable.</li>
<li><strong>Return type</strong>: The <code>findRelevant</code> method should take a {@link dev.langchain4j.store.embedding.EmbeddingSearchRequest} and return a {@link dev.langchain4j.store.embedding.EmbeddingSearchResult}. This would ensure that we have consistency through all embedding stores and allow advanced filtering and reranking features.</li>
<li><strong>Interface segregation</strong>: Introduce a <code>EmbeddingStoreWithFiltering</code> interface, that extends <code>EmbeddingStore</code> and adds a <code>removeAll(Filter filter)</code> method.</li>
<li><strong>Metadata handling:</strong> Implement the <code>MetadataHandler</code> interface, or extend one of its base classes if it&rsquo;s a good fit.</li>
<li><strong>Support Vector Search API in different database modes:</strong> Support vector search in the different modes of Cosmos DB, implementing the search in each of them.</li>
<li><strong>Implement the <code>close()</code> method:</strong> You should close the connection to Firestore in the <code>close()</code> method.</li>
<li><strong>Test your embedding store against the <code>EmbeddingStoreWithFilteringIT</code> and <code>EmbeddingStoreWithRemovalIT</code>:</strong> The provided <code>langchain4j-core</code> contains those abstract test classes that you can use to test your embedding store.</li>
<li><strong>Add documentation:</strong> Create Markdown files in the <code>docs/docs/integrations/embedding-stores</code> directory, following the structure of existing integrations.</li>
<li><strong>Add an example:</strong> to the <a href="https://github.com/langchain4j/langchain4j-examples">examples repository</a>.</li>
</ul>
<p>By following these steps and adapting the provided example code, you can create a robust and well-integrated
LangChain4j module for Google Cloud Firestore.</p>
</details>

<h2 id="second-verdict">Second verdict</h2>
<p><strong>Again, it&rsquo;s pretty spot on and acurate!</strong>
Still a mention to Lombok, or a strange reference to Cosmos DB, but I generally like how it advises me to look at existing modules, telling me that this particular one may be a good inspiration to get my implementation right.</p>
<p>I&rsquo;ve never implemented an embedding store so far, and I think newer models should implement a <code>search()</code> method instead of <code>findRelevant()</code>.
So I asked how to implement this method, using the documentation of the Firestore Java SDK, and a minute later, it suggested a concrete implementation.
I won&rsquo;t copy the output again, as I don&rsquo;t want to make this article too long (it already is).
I haven&rsquo;t tried this implementation, but the code and explanations seemed pretty convincing, so when I get some time, I&rsquo;ll try to see if it&rsquo;s correct.</p>
<h2 id="conclusion">Conclusion</h2>
<p><strong>Only Gemini proposes a 2 million token context window</strong>. And I&rsquo;m glad it does, because the LangChain4j projects is close to that limit!
With models with smaller windows, I would have had to be way more selective, and send in the prompt just the right types of artifacts (ie. just the LLM modules, or just the embedding store implementations). Thanks to the huge window, I was able to feed the whole repository in its entirety!</p>
<p><strong>Does it mean it&rsquo;s the end to writing proper tutorials or how-to guides? Certainly not.</strong>
But I find that very interesting that I&rsquo;m able to have this kind of highly detailed conversation with the LLM, without having to understand all the tiny little details of the underlying project, as the model is able to grok it for me, and distills just the right level of information for me to do the task I asked about.</p>
<p>What&rsquo;s very interesting is that <strong>I can continue the conversation</strong> to go in various directions, or <strong>zoom on some specific aspects</strong>, which may not necessarily be covered by existing tutorials or guides. <strong>It&rsquo;s as if I was pair programming with the founder of the project.</strong></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Generative AI Agent with a real declarative workflow</title><link>https://glaforge.dev/posts/2025/01/31/a-genai-agent-with-a-real-workflow/</link><pubDate>Fri, 31 Jan 2025 13:06:26 +0100</pubDate><guid>https://glaforge.dev/posts/2025/01/31/a-genai-agent-with-a-real-workflow/</guid><description>&lt;p>In my &lt;a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">previous article&lt;/a>, I detailed how to build an AI-powered short story generation &lt;strong>agent&lt;/strong> using Java, &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>, Gemini, and Imagen 3, deployed on Cloud Run jobs.&lt;/p>
&lt;p>This approach involved writing &lt;strong>explicit&lt;/strong> Java code to orchestrate the entire workflow, defining each step programmatically. This follow-up article explores an alternative, &lt;strong>declarative&lt;/strong> approach using &lt;a href="https://cloud.google.com/workflows">Google Cloud Workflows&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ve &lt;a href="https://glaforge.dev/tags/workflows/">written extensively on Workflows&lt;/a> in the past, so for those AI agents that exhibit a very explicit plan and orchestration, I believe Workflows is also a great approach for such declarative AI agents.&lt;/p></description><content:encoded>
<![CDATA[<p>In my <a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">previous article</a>, I detailed how to build an AI-powered short story generation <strong>agent</strong> using Java, <a href="https://docs.langchain4j.dev/">LangChain4j</a>, Gemini, and Imagen 3, deployed on Cloud Run jobs.</p>
<p>This approach involved writing <strong>explicit</strong> Java code to orchestrate the entire workflow, defining each step programmatically. This follow-up article explores an alternative, <strong>declarative</strong> approach using <a href="https://cloud.google.com/workflows">Google Cloud Workflows</a>.</p>
<p>I&rsquo;ve <a href="https://glaforge.dev/tags/workflows/">written extensively on Workflows</a> in the past, so for those AI agents that exhibit a very explicit plan and orchestration, I believe Workflows is also a great approach for such declarative AI agents.</p>
<h2 id="from-imperative-to-declarative-defining-the-workflow">From imperative to declarative: defining the workflow</h2>
<p>The Java-based agent employed an imperative style, where the code explicitly defined the sequence of operations. Each step, from story conception to image selection, was a method call within the <code>ExplicitStoryGeneratorAgent</code> class (you can check the <a href="https://github.com/glaforge/short-genai-stories/blob/main/fictionStoryAgent/src/main/java/storygen/ExplicitStoryGeneratorAgent.java">code of this class</a>). This provided fine-grained control and allowed for parallelization.</p>
<p><a href="https://cloud.google.com/workflows">Cloud Workflows</a> offers a declarative approach. Instead of writing code, you define the workflow in a YAML file. This file specifies the steps, their inputs and outputs, and the order in which they should be executed. You can also easily create loops (sequential or parallel), and you can implement <em>human in the loop</em> callbacks if needed. The workflow engine then interprets this definition and executes the steps accordingly.</p>
<blockquote>
<p>YAML can be a fair bit more cumbersome to write, compared to using a programming language, but non-developers could get a workflow definition rolling, without having to fire an IDE to code. But for a Java developer, it&rsquo;s certainly simpler to write code, with the help of their favorite programming environment.</p></blockquote>
<p>In this article, I&rsquo;ve created a simplified variant: I removed the <em>LLM-as-judge</em> step that picked the best images. And I&rsquo;ve created pictures for the whole story, not for each chapter. So it&rsquo;s not exactly the same agent workflow as in the preivous article. But I don&rsquo;t want you to drown in too much YAML!</p>
<p>The workflow:</p>
<ul>
<li>creates the story with <strong>Gemini 2</strong>,</li>
<li>creates a prompt (for the whole story, not for each chapter),</li>
<li>generates images with <strong>Imagen 3</strong>,</li>
<li>saves the result in <a href="https://cloud.google.com/firestore">Cloud Firestore</a></li>
</ul>
<p>Let&rsquo;s have a look at the full YAML definition, and read the comments explaining what each step does:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Let&#39;s define the Gemini and Image models we want to use:</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">setup</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">GEMINI_MODEL</span>:<span style="color:#bbb"> </span>${&#34;projects/&#34; + sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;) +<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;/locations/us-central1/publishers/google/models/gemini-2.0-flash-exp&#34;</span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">IMAGEN_MODEL</span>:<span style="color:#bbb"> </span>${&#34;projects/&#34; + sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;) +<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;/locations/us-central1/publishers/google/models/imagen-3.0-generate-002&#34;</span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># We call Gemini to generate the story</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">generate_story</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.aiplatform.v1.projects.locations.endpoints.generateContent<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">model</span>:<span style="color:#bbb"> </span>${GEMINI_MODEL}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">region</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;us-central1&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">contents</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">role</span>:<span style="color:#bbb"> </span>user<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">parts</span>:<span style="color:#bbb">  </span><span style="color:#60a0b0;font-style:italic"># Let&#39;s write a sci-fi story!</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>- <span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Write a short science-fiction story&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">generationConfig</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">temperature</span>:<span style="color:#bbb"> </span><span style="color:#40a070">2.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">responseMimeType</span>:<span style="color:#bbb"> </span>application/json<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#60a0b0;font-style:italic"># Use a JSON schema to define the format of the output</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">responseSchema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>OBJECT<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">properties</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>STRING<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span>The title of the short story<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>STRING<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span>The body of the story<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb"> </span>[<span style="color:#4070a0">&#39;title&#39;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;content&#39;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#60a0b0;font-style:italic"># You can define system instructions</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">systemInstruction</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">parts</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>- <span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>&gt;<span style="color:#4070a0;font-style:italic">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  You are a creative fiction author,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  and your role is to write stories.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  You write a story as requested by the user.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  A story always has a title,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  and is made of 5 long chapters.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  Each chapter has a title, is split into paragraphs,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  and is at least 20 sentences long.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>short_story<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">     </span><span style="color:#60a0b0;font-style:italic"># Assign the story, title, content into some variables</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">get_story</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">story_output</span>:<span style="color:#bbb"> </span>${json.decode(short_story.candidates[0].content.parts[0].text)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb"> </span>${story_output.title}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">content</span>:<span style="color:#bbb"> </span>${story_output.content}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Let&#39;s call Gemini again, but for creating a prompt for Imagen</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">generate_image_prompt</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.aiplatform.v1.projects.locations.endpoints.generateContent<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">model</span>:<span style="color:#bbb"> </span>${GEMINI_MODEL}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">region</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;us-central1&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">contents</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">role</span>:<span style="color:#bbb"> </span>user<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">parts</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>- <span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>${content}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">systemInstruction</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">parts</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>- <span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>|<span style="color:#4070a0;font-style:italic">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  You are an expert artist who masters crafting great
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  prompts for image generation models, to illustrate
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  short stories.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  When given a short story, reply with a concise
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  prompt that could be used to create an illustration
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  with the Imagen 3 model.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  Don&#39;t use any flags like those used with MidJourney.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  Just answer with the short concise text prompt.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  Your answer MUST start with &#34;A cartoon of &#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  as we want to use cartoon or comics illustrations.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  The user gives you the following image prompt
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic">                  for the chapter to illustrate:</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>image_prompt<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Retrieve the prompt from Gemini&#39;s output</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">assign_prompt</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">prompt</span>:<span style="color:#bbb"> </span>${image_prompt.candidates[0].content.parts[0].text}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Time to generate the images</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">image_generation</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.aiplatform.v1.projects.locations.endpoints.predict<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">endpoint</span>:<span style="color:#bbb"> </span>${IMAGEN_MODEL}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">region</span>:<span style="color:#bbb"> </span>us-central1<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">instances</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">prompt</span>:<span style="color:#bbb"> </span>${prompt}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">parameters</span>:<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># Store images in Google Cloud Storage</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">storageUri</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;gs://short-scifi-stories-generated-images&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>images<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Utility step to create the picture data for Firestore</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">prepare_images_uri_list</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">create_empty_list</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">uris</span>:<span style="color:#bbb"> </span>[]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">uris_for_firestore</span>:<span style="color:#bbb"> </span>[]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">loop_over_images</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">for</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">value</span>:<span style="color:#bbb"> </span>img_object<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span>${images.predictions}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>- <span style="color:#062873;font-weight:bold">append_uri</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span>- <span style="color:#062873;font-weight:bold">uris</span>:<span style="color:#bbb"> </span>${list.concat(uris, img_object.gcsUri)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span>- <span style="color:#062873;font-weight:bold">stringUriMap</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                      </span><span style="color:#062873;font-weight:bold">stringValue</span>:<span style="color:#bbb"> </span>${img_object.gcsUri}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span>- <span style="color:#062873;font-weight:bold">uris_for_firestore</span>:<span style="color:#bbb"> </span>${list.concat(uris_for_firestore, stringUriMap)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Let&#39;s prepare the final output to return</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># as the result of the workflow execution</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">prepare_result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">final_result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb"> </span>${title}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">content</span>:<span style="color:#bbb"> </span>${content}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">prompt</span>:<span style="color:#bbb"> </span>${prompt}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">images</span>:<span style="color:#bbb"> </span>${uris}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">createdAt</span>:<span style="color:#bbb"> </span>${sys.now()}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Finally, let&#39;s save the story in Firestore</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">save_to_firestore</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.firestore.v1.projects.databases.documents.createDocument<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">collectionId</span>:<span style="color:#bbb"> </span>short-story<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">parent</span>:<span style="color:#bbb"> </span>${&#34;projects/&#34; + sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;) + &#34;/databases/(default)/documents&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">documentId</span>:<span style="color:#bbb"> </span>${uuid.generate()}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">fields</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">stringValue</span>:<span style="color:#bbb"> </span>${final_result.title}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">stringValue</span>:<span style="color:#bbb"> </span>${final_result.content}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">prompt</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">stringValue</span>:<span style="color:#bbb"> </span>${final_result.prompt}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">images</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">arrayValue</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">values</span>:<span style="color:#bbb"> </span>${uris_for_firestore}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">createdAt</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">timestampValue</span>:<span style="color:#bbb"> </span>${time.format(final_result.createdAt,&#34;GMT&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># Return the data</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_output</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${final_result}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This YAML file defines the entire story generation process. It calls the Gemini and Imagen APIs, extracts the necessary information from the responses, and saves the final result to Firestore. No Java code is required to manage the flow of execution.</p>
<h2 id="key-differences-and-trade-offs">Key differences and trade-offs</h2>
<p>Let&rsquo;s zoom in on the pros and cons of both approaches.</p>
<h3 id="imperative--programming-approach">Imperative / programming approach:</h3>
<ul>
<li><strong>Pros</strong>:
<ul>
<li>Fine-grained control over the workflow.</li>
<li>Explicit parallelization for performance optimization.</li>
<li>Familiar programming and debugging tools.</li>
<li>Cloud Run jobs is fully managed and scaled by Google Cloud.</li>
<li>Job execution can be scheduled by Cloud Scheduler.</li>
</ul>
</li>
<li><strong>Cons</strong>:
<ul>
<li>You need to be familiar with a programming language &amp; environment.</li>
<li>It can potentially be challenging to maintain as the workflow evolves.</li>
<li>The approach used required being familiar with running &amp; scheduling containers as jobs.</li>
</ul>
</li>
</ul>
<h3 id="declarative--workflow-based-approach">Declarative / workflow based approach:</h3>
<ul>
<li><strong>Pros</strong>:
<ul>
<li>Pretty easy-to-understand workflow definitions.</li>
<li>Workflows offers a visualisation of the steps (also during execution).</li>
<li>Parallelization can be defined explicitly (with the <code>parallel</code> keyword on iterations or step branches).</li>
<li>Simplified maintenance and updates. Just need to update the YAML in the console.</li>
<li>Workflows is scalable and reliable out of the box without extra effort.</li>
<li>Workflow execution can be scheduled by Cloud Scheduler.</li>
</ul>
</li>
<li><strong>Cons</strong>:
<ul>
<li>YAML authoring can be painful, if you&rsquo;re not familiar with the APIs you call.</li>
<li>Parallelization is declarative but might be limited depending on the workflow definition and Google Cloud Workflows capabilities. You would have more control with a programming language.</li>
<li>There&rsquo;s no emulator to run workflows locally, so you might have to create copies and work on these, to not affect the production workflow.</li>
<li>Debugging relies on workflow execution logs, which might be less intuitive than traditional debugging.</li>
</ul>
</li>
</ul>
<h2 id="choosing-the-right-approach">Choosing the right approach</h2>
<p>It depends! &#x1f605;</p>
<p>Of course, the choice between these approaches depends on the specific project requirements. If fine-grained control and explicit parallelization are critical, the imperative programming approach might be preferable.</p>
<p>However, for simpler workflows where ease of development and maintainability are critical, Cloud Workflows offers an interesting alternative. You can easily make a tweak to the workflow directly from the Google Cloud console if needed.</p>
<p>In the case of this story generation agent, the declarative approach sounds like a good fit, but the YAML authoring can be a bit painful at times, as you have to look up the various payload schemas for the APIs to invoke, to be able to make the service calls. But that&rsquo;s definitely a plus as well, in the sense that pretty much all the products and services offered on Google Cloud Platform can easily be called via REST endpoints, and Workflows excels at that.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Explicit declarative planning helps AI agents stay focused, and ensures a high level of predictability.
My experience with agents which plan their own actions has been mixed, as sometimes the LLM hallucinates function calls, or calls functions with bogus parameters.
In the previous <a href="https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/">previous article</a>, I used an imperative programming approach, but in this article today, I developed a simplified equivalent with a declarative workflow definition.</p>
<p><a href="https://cloud.google.com/workflows">Google Cloud Workflows</a> offers a powerful and convenient way to build and manage declarative AI agents — and obviously any other kind of process that needs to call APIs. By defining the workflow declaratively, you can focus on the logic of your agent rather than the details of execution. While it might not be suitable for every use case, it&rsquo;s definitely a valuable tool to consider when building AI-powered applications on Google Cloud!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>An AI agent to generate short sci-fi stories</title><link>https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/</link><pubDate>Mon, 27 Jan 2025 08:06:40 +0100</pubDate><guid>https://glaforge.dev/posts/2025/01/27/an-ai-agent-to-generate-short-scifi-stories/</guid><description>&lt;p>This project demonstrates how to build a fully automated short story generator using Java, &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>, Google Cloud&amp;rsquo;s &lt;strong>Gemini&lt;/strong> and &lt;strong>Imagen 3&lt;/strong> models, and a serverless deployment on &lt;a href="http://cloud.run/">Cloud Run&lt;/a>.&lt;/p>
&lt;p>Every night at midnight UTC, a new story is created, complete with AI-generated illustrations, and published via Firebase Hosting.
So if you want to read a new story every day, head over to:&lt;/p>
&lt;h2 id="-short-ai-storywebapphttpsshort-ai-storywebapp-">→ &lt;a href="https://short-ai-story.web.app/">short-ai-story.web.app&lt;/a> ←&lt;/h2>
&lt;p>The code of this agent is available on Github. So don&amp;rsquo;t hesitate to &lt;strong>check out the code&lt;/strong>:&lt;/p></description><content:encoded>
<![CDATA[<p>This project demonstrates how to build a fully automated short story generator using Java, <a href="https://docs.langchain4j.dev/">LangChain4j</a>, Google Cloud&rsquo;s <strong>Gemini</strong> and <strong>Imagen 3</strong> models, and a serverless deployment on <a href="http://cloud.run/">Cloud Run</a>.</p>
<p>Every night at midnight UTC, a new story is created, complete with AI-generated illustrations, and published via Firebase Hosting.
So if you want to read a new story every day, head over to:</p>
<h2 id="-short-ai-storywebapphttpsshort-ai-storywebapp-">→ <a href="https://short-ai-story.web.app/">short-ai-story.web.app</a> ←</h2>
<p>The code of this agent is available on Github. So don&rsquo;t hesitate to <strong>check out the code</strong>:</p>
<h2 id="-githubcomglaforgeshort-genai-storieshttpsgithubcomglaforgeshort-genai-stories-">→ <a href="https://github.com/glaforge/short-genai-stories">github.com/glaforge/short-genai-stories</a> ←</h2>
<p>Let&rsquo;s have a closer look at the architecture and workflow of this automated storytelling machine.</p>
<h2 id="the-agent-the-storytellers-brain">The agent: the storyteller&rsquo;s brain</h2>
<p>At the heart of the system lies the <code>ExplicitStoryGeneratorAgent</code>, a Java class orchestrating the entire story generation process. This agent follows a clear, multi-step workflow:</p>
<ul>
<li><strong>Story conception (Gemini)</strong>: The agent first calls the Gemini large language model (LLM) to generate the core story elements: a title, five chapters each with title and content.</li>
<li><strong>Image prompt engineering (Gemini)</strong>: For each chapter&rsquo;s content, the agent again leverages Gemini to craft tailored image generation prompts. This ensures that the image prompts are relevant to the specific content of each chapter.</li>
<li><strong>Illustration generation (Imagen 3)</strong>: Using the generated prompts, the agent calls Imagen 3 to produce a set of image candidates (four by default) for each chapter.</li>
<li><strong>Image selection (Gemini, self-reflection)</strong>: In a unique <em>&ldquo;self-reflection&rdquo;</em> step, the agent presents the generated images back to Gemini, asking the LLM to select the image that best visually represents the chapter&rsquo;s narrative. This crucial step ensures that the illustrations truly complement the story.</li>
<li><strong>Persistence (Firestore)</strong>: Once the story, chapter titles, content, and selected images are finalized, the agent stores them in a Firestore database (a NoSQL document database). This makes retrieving complete data relatively straightforward from the web frontend, thanks to the <a href="https://firebase.google.com/">Firebase</a> framework.</li>
</ul>
<p>For the more visual people among us, this diagram illustrates the steps above:</p>
<p><figure>
  <a href="#img-f645caa4943d6835f8238b80e9b91aae">
    <img src="/img/short-ai-stories/agent-workflow.png"
      alt="Agent workflow diagram"
       />
  </a>
  <figcaption>Agent workflow diagram</figcaption>
</figure>
<div class="lightbox" id="img-f645caa4943d6835f8238b80e9b91aae">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/short-ai-stories/agent-workflow.png"
    alt="Agent workflow diagram"
     />
  <div class="lightbox-caption">Agent workflow diagram</div>
</div>
</p>
<p><strong>Note:</strong> The critique step where Gemini is asked to judge the best image isn&rsquo;t really necessary, to be honest. Imagen generates images that adhere very much to the given prompt. So either of them would seem suitable to illustrate each chapter. But it was interesting to implement a <em>self-reflection</em> step in this workflow.</p>
<h2 id="digression-explicit-vs-autonomous-agent-workflows">Digression: Explicit vs. Autonomous Agent Workflows</h2>
<p>This project utilizes an <em>explicit workflow agent</em>, where the story generation process is meticulously defined and controlled by the Java code. This approach contrasts with fully <em>autonomous agents</em>, which rely on the LLM to plan and execute the workflow dynamically.</p>
<p>Let&rsquo;s explore the key differences and trade-offs between these two approaches:</p>
<h3 id="explicit-workflow-agent-code-driven-planning">Explicit workflow agent (code-driven planning):</h3>
<ul>
<li><strong>Predictable execution</strong>: The Java code dictates the exact sequence of steps, ensuring a highly predictable and reliable workflow. Each stage, from story conception to image selection, is explicitly programmed, leaving no room for unexpected deviations.</li>
<li><strong>Improved performance through parallelization</strong>: With explicit control, tasks that can be executed concurrently (such as generating images for different chapters or judging the best image for each chapter) can be easily parallelized. This significantly reduces the overall execution time.</li>
<li><strong>Easier debugging and maintenance</strong>: The clear, structured code makes debugging and maintenance straightforward. The flow of execution is transparent to the developer, and any errors can be readily identified and addressed.</li>
<li><strong>Limited flexibility</strong>: The explicit nature of the workflow could be seen as offering less flexibility. Indeed, the code needs to be updated to handle changes of the workflow. However, it&rsquo;s not necessarily worse than endlessly tweaking prompts to coerce an LLM to plan correctly the needed workflow changes.</li>
</ul>
<h3 id="autonomous-agent-llm-driven-planning">Autonomous agent (LLM-driven planning):</h3>
<ul>
<li><strong>Dynamic workflow</strong>: Autonomous agents use the LLM&rsquo;s capabilities to plan and execute the workflow. This allows for greater flexibility and adaptability to different story generation requirements. The LLM can theoretically decide which steps to take, in which order, and how many times.</li>
<li><strong>Potential for hallucinations and errors</strong>: Relying on the LLM for planning introduces the risk of hallucinations and incorrect function calls. The LLM might generate nonsensical steps, omit crucial actions, provide incorrect parameters to functions, or execute functions in an illogical order. This can lead to unpredictable results and make it harder to catch potential errors. Even with perfect prompts, LLMs might make mistakes in function calling. This is actually the problem I encountered when trying this approach first.</li>
<li><strong>Debugging challenges</strong>: Debugging autonomous agents can be more complex. The dynamic nature of the workflow makes it harder to trace the execution path and identify the source of errors. Troubleshooting often involves analyzing the logs of the LLM and the tools it requested to call, which can be challenging to interpret at times.</li>
<li><strong>Less control over execution</strong>: With autonomous agents, developers cede some control over the execution flow to the LLM. While this offers flexibility, it also means less fine-grained control over performance optimization. Parallelization opportunities, for example, might not be readily apparent or easily exploitable. Currently, when receiving paralell function call requests, LangChain4j doesn&rsquo;t yet offer the possibility to request their paralellization.</li>
</ul>
<p>The autonomous approach would have looked like the following diagram:</p>
<p><figure>
  <a href="#img-d77b5566f0d72b614e9c8e187c39bb59">
    <img src="/img/short-ai-stories/autonomous-approach.png"
      alt="Autonomous agent diagram"
       />
  </a>
  <figcaption>Autonomous agent diagram</figcaption>
</figure>
<div class="lightbox" id="img-d77b5566f0d72b614e9c8e187c39bb59">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/short-ai-stories/autonomous-approach.png"
    alt="Autonomous agent diagram"
     />
  <div class="lightbox-caption">Autonomous agent diagram</div>
</div>
</p>
<p>With this approach, the main agent generates the story, then would call first the prompt creation tool, then the generation image tool, and would finish with the tool to pick up the best image. However, in my experience, in spite of a good amount of prompt engineering tweaks, I couldn&rsquo;t get this to work reliably. I tried with different versions of Gemini (1.5 Flash, 1.5 Pro, and 2.O Flash experimental, from worst to best outcome), but sometimes, for example, it would request to judge images before they had been generated, or the URLs of the images would be hallucinated instead of coming from the outcome of the judge. So I prefered moving to a more explicit approach.</p>
<p>I invite you to read this great article from Anthropic about <a href="https://www.anthropic.com/research/building-effective-agents">building effective agents</a> which also makes the distiction between <em>agents</em> (fully autonomous planning agents) and <em>workflows</em> (the more explicit approach with code driving the execution planning). They also recommend to stick to <em>workflows</em> when the logic of the agent is very clear upfront: when you can draw a workflow on a sheet of paper, that&rsquo;s surely because you do need a workflow.</p>
<h3 id="choosing-the-right-approach">Choosing the right approach:</h3>
<p>The choice between explicit and autonomous workflows depends on the specific requirements of the project. Explicit workflows are best suited for applications where predictability, reliability, and performance are paramount.</p>
<p>Autonomous agents are more appropriate when flexibility, adaptability, and dynamic planning are prioritized, even at the cost of potential errors and increased debugging complexity.</p>
<p>This project prioritizes the former over the latter, which explains why an explicit agent is preferred for this use case. In other words, it&rsquo;s better to have a predictable solution, which then can be easily parallelized to reduce latency, than a non-predictable solution that is slower.</p>
<h2 id="a-closer-look-at-the-code">A closer look at the code</h2>
<p>As you&rsquo;ll be exploring the <a href="https://github.com/glaforge/short-genai-stories">code base</a>, I&rsquo;d like to highlight a few points.</p>
<p>The <code>ExplicitStoryGeneratorAgent</code> class uses a structured and predictable approach to orchestrating the LLM. Its core logic resides within the <code>main()</code> method, outlining a clear, step-by-step workflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// 1️⃣ let&#39;s prepare the story</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Story<span style="color:#bbb"> </span>story<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>prepareStory(<span style="color:#4070a0">&#34;a science-fiction novel&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 2️⃣ iterate over each chapter in parallel</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Story.<span style="color:#4070a0">Chapter</span><span style="color:#666">&gt;</span><span style="color:#bbb"> </span>newChaptersWithImages<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>story.<span style="color:#4070a0">chapters</span>.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">parallel</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">map</span>(chapter<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 3️⃣ prepare an impage prompt for each chapter</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>imagePrompt<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>prepareImagePromptForChapter(chapter);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 4️⃣ generate up to 4 images per chapter</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imagesForChapter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>generateImages(imagePrompt);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// 5️⃣ judge the best image for this chapter</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>bestImage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>pickBestImageForChapter(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>chapter.<span style="color:#4070a0">chapterContent</span>,<span style="color:#bbb"> </span>imagesForChapter);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Story.<span style="color:#4070a0">Chapter</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>chapter.<span style="color:#4070a0">chapterTitle</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>chapter.<span style="color:#4070a0">chapterContent</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>bestImage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}).<span style="color:#4070a0">toList</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Story<span style="color:#bbb"> </span>newStoryWithImages<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Story(story.<span style="color:#4070a0">title</span>,<span style="color:#bbb"> </span>newChaptersWithImages);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// 6️⃣ save the story to Firestore</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>saveToFirestore(newStoryWithImages);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Story generation depends on <em>structured output</em>: The agent uses Gemini to generate the story&rsquo;s title and five chapters, each with a title and content. Crucially, it leverages Java <code>record</code>s and <code>responseSchema</code> to ensure type safety and consistent outputs. You&rsquo;ll notice the use of <code>@Description</code> annotations to ensure the LLM really understands what each field corresponds to:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Story</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;The title of the story&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>title,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;The chapters of the story&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List<span style="color:#666">&lt;</span>Chapter<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>chapters)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Chapter</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;The title of the chapter&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>chapterTitle,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;The content of the chapter&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>chapterContent,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;The Google Cloud Storage URI of the image...&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>gcsURI)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>To configure the model generation to use structured outputs, here&rsquo;s how the schema of this output is defined:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>chatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(GCP_PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(GCP_LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(CHAT_MODEL_NAME)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">temperature</span>(1.<span style="color:#4070a0">5f</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;title&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#4070a0">&#34;The title of the story&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;chapters&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#4070a0">&#34;The list of 5 chapters&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">ARRAY</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setItems</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#4070a0">&#34;A chapter with a title, and its content&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;chapterTitle&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#4070a0">&#34;The title of the chapter&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;chapterContent&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#4070a0">&#34;The content of the chapter, &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#4070a0">&#34;made of 20 sentences&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">addAllRequired</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;chapterTitle&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;chapterContent&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">addAllRequired</span>(List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;title&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;chapters&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>It&rsquo;s possible to simplify the schema creation by taking advantage of a helper class. This schema could have been simplified to:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(SchemaHelper.<span style="color:#4070a0">fromClass</span>(Story.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>To instruct the LLM at each step, I tend to use system instructions for setting the role and goal for the LLM, but I use user messages to give the more variable part, like the chapter&rsquo;s content, or the image prompt. Here&rsquo;s an example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a creative fiction author, and your role is to write stories.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You write a story as requested by the user.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        A story always has a title, and is made of 5 long chapters.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Each chapter has a title, is split into paragraphs, \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and is at least 20 sentences long.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(storyType)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>storyType</code> variable in the user message contains the type of story to generate, like <code>&quot;a science-fiction story&quot;</code>.
It&rsquo;s currently set in stone, but you could parameterize this to generate fantasy novels, love stories, etc.</p>
<p>The <em>self-reflection</em> step, where the LLM judges which is the best illustration for a chapter is taking advantage of Gemini&rsquo;s multimodal capabilities.
Indeed, Gemini receives the instruction of picking the best image out of a few, and it is given the text of the request (and the URLs of the pictures), as well as inline references to those images (ie. the Google Cloud Storage URI, pointing at the location of the pictures). Thus, this is a multimodal request, as both text and images are passed in the prompt:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imagesForChapter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>generateImages(imagePrompt);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>bestImage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>pickBestImageForChapter(chapter.<span style="color:#4070a0">chapterContent</span>,<span style="color:#bbb"> </span>imagesForChapter);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Inside pickBestImageForChapter we have:</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>messages<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messages.<span style="color:#4070a0">add</span>(SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;...prompt to select best image...&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messages.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;...chapter content...&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>imagesForChapter.<span style="color:#4070a0">forEach</span>(imageUrl<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Send each URL as text and as image to the model</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>messages.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(imageUrl<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\n&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>messages.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(ImageContent.<span style="color:#4070a0">from</span>(imageUrl)));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">generate</span>(messages);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// ... parse best image from response</span><span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="building-the-application">Building the application</h2>
<p>The project employs a standard Java development workflow using Maven for dependency management and building:</p>
<ul>
<li><strong>Dependencies</strong>: The <code>pom.xml</code> file defines the project&rsquo;s dependencies, including LangChain4j (for LLM orchestration), the Google Cloud Firestore library (for data persistence), and Google Cloud&rsquo;s Gemini and Imagen libraries.</li>
<li><strong>Packaging</strong>: The Maven build process packages the application into a JAR, and its dependencies by its side. I followed the approach explained in that <a href="https://medium.com/holisticon-consultants/dont-build-fat-jars-for-docker-applications-6252a5571248">article</a>: to build a JAR with its dependencies on the side, instead of a <em>shaded / fat</em> JAR. One benefit I see is that the dependencies are one container layer, while the application itself is another, so it should make Docker building layer faster, as the dependencies don&rsquo;t change often, and that dependency layer would be cached.</li>
<li><strong>Containerization (Docker)</strong>: A <code>Dockerfile</code> is used to containerize the application. The container image includes the executable JAR and dependencies, as well as the Java runtime environment. I used Azul&rsquo;s <a href="https://hub.docker.com/layers/azul/zulu-openjdk-distroless/21-latest/images/sha256-986ca559b15738378f4a67a563c117159136c5eff705b91ba68f0fe8883246ef?context=explore">Zulu distroless Java 21</a> base image. The container is finally built thanks to Cloud Build.</li>
</ul>
<h2 id="deployment-and-automation">Deployment and automation</h2>
<p>To automate story generation and deployment, the project leverages several Google Cloud services:</p>
<ul>
<li><strong>Cloud Build</strong>: Cloud Build automates the process of building the Docker container image. The provided <code>justfile</code> contains commands and recipes to build and submit the container image (I covered <code>just</code> in a <a href="https://glaforge.dev/posts/2023/06/07/just-a-handy-command-line-tool/">previous article</a>, a nifty little tool to parameterize and run common commands for the project). I simply followed the tutorial in the Cloud Build documentation to <a href="https://cloud.google.com/build/docs/running-builds/submit-build-via-cli-api">submit a build via the CLI</a> (the <code>gcloud</code> CLI SDK), after having done some IAM setup as explained <a href="https://cloud.google.com/build/docs/build-push-docker-image">here</a> to be able to push the built image in <a href="https://cloud.google.com/artifact-registry/docs">Artifact Registry</a>.</li>
<li><strong>Cloud Run jobs</strong>: The application runs as a Cloud Run job. Contrary to Cloud Run services, where incoming HTTP requests trigger the service, here, jobs are triggered and run to completion. The Cloud Run job allows for serverless execution of the story generation agent. I followed this guide to <a href="https://cloud.google.com/run/docs/create-jobs#gcloud">create jobs</a>. Don&rsquo;t forget to set up the <a href="https://cloud.google.com/run/docs/configuring/jobs/environment-variables">required environment variables</a>.</li>
<li><strong>Cloud Scheduler</strong>: Cloud Scheduler triggers the Cloud Run job every day at midnight UTC. This automation ensures that a new story is generated and published daily. To configure this, this page explains how to set <a href="https://cloud.google.com/run/docs/execute/jobs-on-schedule#command-line">scheduled triggers</a>.</li>
<li><strong>Firebase Hosting</strong>: Firebase Hosting serves the static assets of the website (HTML, CSS, JavaScript) that displays the stories. Firebase also provides easy access to the Firestore database where the stories are stored, at the last stage of our agentic workflow.</li>
</ul>
<h2 id="further-possible-improvements">Further possible improvements</h2>
<p>I&rsquo;m not in the business of selling novels, so I won&rsquo;t really spend much more time improving this application. However, I noticed a few areas where this project could be improved.</p>
<h3 id="more-creativity">More creativity</h3>
<p>When reading the short stories, you&rsquo;ll notice a certain lack of creativity. Somehow, the stories often happen around the years 2340, the action takes places on Xylos, and some characters appear very frequently, like Aris Thorne. Similarly, some words or concepts appear all the time, like the words <em>echoes</em>, <em>obsidian</em>, <em>crimson</em>, etc. Maybe the model has seen such novels, with such personas, locations, time period, in its training. I&rsquo;ve seen online some people getting the <a href="https://www.semanticpen.com/tools/ai-generated-superhero-origin-story-generator/female-superhero-origin-story--e894532b-b775-4fc8-82d4-e0afcb31afad">same kind of stories</a>, and even a <a href="https://www.amazon.com/Veins-Starlight-Forbidden-Astral-Weavers-ebook/dp/B0DPKY5C4R">book</a> with the same characters or location.</p>
<p>I think it&rsquo;d be interesting to explore how to make the stories more diverse and varied. For example by adding more steps in the workflow to work on character creation, on different narrative arcs, on environment definitions. For science-ficiton only, there are tons of <a href="https://www.rachelagreco.com/30-types-of-science-fiction-every-sci-fi-lover-should-know/">types of sci-fi stories</a>. My friend, Philippe Charrière, worked on <a href="https://k33g.hashnode.dev/how-to-generate-random-rpg-character-names-with-an-llm">how to generate random RPG character names with LLMs</a>. He shared plenty of ideas on how to guide LLMs to get more creative with personas.</p>
<h3 id="character-definition-for-illustration-consistency">Character definition for illustration consistency</h3>
<p>Speaking of character creation, if you look at the illustrations, you&rsquo;ll see that the characters often don&rsquo;t have the same style or appearance. Indeed, I don&rsquo;t give Imagen the whole context of the story when I let Gemini create the image prompts. A possible area of improvement could be to work on proper character definitions (face characteristics, attire, etc.), and ensure that the information is passed through to Imagen. The same would apply for the setting, like the planet, the spaceship details, and more.</p>
<h3 id="chapter-legibility">Chapter legibility</h3>
<p>Each story is split into 5 chapters, of about 20 sentences or so. I tried to make Gemini to generate paragraphs, to improve legibility. However, in spite of a bit of time spent on tweaking the prompts, I failed to coerce it to create paragraphs to delineate the key sections of the chapters. When prompting can&rsquo;t solve this, an extra LLM call loop can take the chapter&rsquo;s content and make it more readable.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The key take away of this experimetnation, is that <strong>when you can describe your AI agent&rsquo;s plan of action with an explicit and predictable workflow, you should definitely follow that route, and avoid giving the LLM the freedom to handle the planning alone</strong>. LLM autonomous planning works much better in more unpredictable cases, where steps can&rsquo;t be foreseen. Be sure to use the right approach!</p>
<p>Again, Gemini and Imagen were up to the task for this new project and gave great stories and illustrations, even if the creativity could be improved. And I&rsquo;ll keep using <a href="https://docs.langchain4j.dev/">LangChain4j</a> as my Swiss-army knife for all my Generative AI projects, as it works reliably, and offers rich capabilities.</p>
<p>Knowing that I would build a <em>workflow</em>, I also explored the use of my beloved <a href="https://cloud.google.com/workflows">Google Cloud Workflows</a> which I&rsquo;ve <a href="https://glaforge.dev/tags/workflows/">written a lot about</a>. I&rsquo;ll likely write another (shorter) article where I&rsquo;ll show how to create such GenAI workflows with it, stay tuned.</p>
<p>This project was also a good opportunity for me to use Cloud Run jobs. I love Cloud Run for all my serverless, auto-scaled, fully-managed, HTTP services, but I hadn&rsquo;t used a Cloud Run <em>job</em> so far. For such <em>batch</em> kind of tasks, this is the right tool for the <em>job</em> (pun intended)! There&rsquo;s also <a href="https://cloud.google.com/batch">Google Cloud Batch</a> but it&rsquo;s more for heavier computation kind of workloads.</p>
<p>So what&rsquo;s next? Checkout the website to <a href="https://short-ai-story.web.app/">read a short story every day</a>, and explore the <a href="https://github.com/glaforge/short-genai-stories">code base</a> to better understand how stories are baked. If you want to replicate this application, and haven&rsquo;t yet tried Google Cloud, feel free to use the <a href="https://cloud.google.com/free">$300 of credits for new users</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Analyzing trends and topics from Bluesky's Firehose with generative AI</title><link>https://glaforge.dev/posts/2025/01/06/analyzing-trends-and-topics-from-blueskys-firehose-with-generative-ai/</link><pubDate>Mon, 06 Jan 2025 10:10:13 +0100</pubDate><guid>https://glaforge.dev/posts/2025/01/06/analyzing-trends-and-topics-from-blueskys-firehose-with-generative-ai/</guid><description>&lt;p>First article of the year, so let me start by wishing you all, my dear readers, a very happy new year!
And what is the subject of this new piece of content?
For a while, I&amp;rsquo;ve been interested in analyzing trends and topics in social media streams.
I recently joined Bluesky (you can follow me at &lt;a href="https://bsky.app/profile/glaforge.dev">@glaforge.dev&lt;/a>),
and contrarily to X, it&amp;rsquo;s possible to access its Firehose
(the stream of all the messages sent by its users) pretty easily, and even for free.
So let&amp;rsquo;s see what we can learn from the firehose!&lt;/p></description><content:encoded>
<![CDATA[<p>First article of the year, so let me start by wishing you all, my dear readers, a very happy new year!
And what is the subject of this new piece of content?
For a while, I&rsquo;ve been interested in analyzing trends and topics in social media streams.
I recently joined Bluesky (you can follow me at <a href="https://bsky.app/profile/glaforge.dev">@glaforge.dev</a>),
and contrarily to X, it&rsquo;s possible to access its Firehose
(the stream of all the messages sent by its users) pretty easily, and even for free.
So let&rsquo;s see what we can learn from the firehose!</p>
<p>Without further ado, here&rsquo;s the end goal!</p>
<p><figure>
  <a href="#img-14c84f280b13e11b1ca0b16e2380ffd1">
    <img src="/img/bluesky/bluesky-visualization.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-14c84f280b13e11b1ca0b16e2380ffd1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bluesky/bluesky-visualization.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="blueskys-firehose--a-stream-of-social-messages">Bluesky&rsquo;s Firehose — a stream of social messages</h2>
<p>The underlying protocol used by Bluesky is the <a href="https://atproto.com/">AT Protocol</a>.
There&rsquo;s an API to access Bluesky&rsquo;s streams via this protocol, but it&rsquo;s a bit cumbersome to use.
In order to reduce the quantity of data sent via the AT protocol over its &ldquo;relay&rdquo; network,
the Bluesky team introduced <a href="https://docs.bsky.app/blog/jetstream">JetStream</a>,
to relay all the messages as well, via WebSockets, in JSON format, for a fraction of the size of the AT protocol payloads.
You can also read about how they <a href="https://jazco.dev/2024/09/24/jetstream/">shrinked the payloads by 99%</a>!</p>
<p>The <a href="https://github.com/bluesky-social/jetstream">JetStream Github repository</a> shares the endpoints you can use to access the firehose,
and gives some details about the various types of payloads (new messages, likes, shares, etc.)
It also mentioned a nice little tool called <a href="https://github.com/vi/websocat">websocat</a>
which is a command line tool to connect to WebSockets — very handy to analyze the payloads.</p>
<p>To better understand the JSON message formats, I used websocat, as well as Simon Willison&rsquo;s client-side
<a href="https://simonwillison.net/2024/Nov/20/bluesky-websocket-firehose/">online tool</a>
to access the JetStream, and see the flows of messages.</p>
<h2 id="a-birds-eye-view-of-the-project">A bird&rsquo;s eye view of the project</h2>
<p>Before diving into the code, and showing how to fetch the Bluesky posts,
I&rsquo;d like to give you a high level overview of what we&rsquo;re going to implement.</p>
<p><figure>
  <a href="#img-ae730e81d2585a5fafa6042c8733d025">
    <img src="/img/bluesky/bluesky-jetstream-trends.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ae730e81d2585a5fafa6042c8733d025">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bluesky/bluesky-jetstream-trends.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<blockquote>
<p>I used <a href="https://www.napkin.ai/">Napkin.ai</a> to generate this diagram!
Go check out this tool. You can paste your article, and for a given paragraph, it can suggest nice diagrams to represent them.</p></blockquote>
<p>The key steps towards a topic visualization of the stream are:</p>
<ol>
<li><strong>Data acquisition</strong> — The first step is to subscribe to the firehose via WebSockets to gather enough data points to make it interesting to extract trends from them.</li>
<li><strong>Embedding messages</strong> — In order to compare users&rsquo; posts, the text of the posts should be transformed into vector embeddings via an embedding model, which represents posts into a multidimensional space in which distances can be calculated (text whose vector is closer to another one is usually semantically similar).</li>
<li><strong>Clustering messages</strong> — Now that we have all the vector embeddings, a clustering algorithm is used to create groups of messages that are close to each other in vector space, and form a cluster of data points on the same topic.</li>
<li><strong>Generating a summary of clusters&rsquo; messages</strong> — The clustering algorithm grouped messages into different clusters. However, at that point, we don&rsquo;t know what all those grouped messages are about. That&rsquo;s where a generative AI model is called to make sense of those messages, to get a short description of them.</li>
<li><strong>Preparing the data for visualization</strong> — Armed with the clusters of posts and their descriptions, the data for the visualization is prepared.</li>
<li><strong>Visualizing the trends</strong> — The last step is to visualize those clusters of messages with a nice visualization. For that purpose, I decided to present the groups of messages as bubbles (the more posts in a bubble, bigger the bubble is).</li>
</ol>
<h2 id="lets-get-coding">Let&rsquo;s get coding!</h2>
<p>In the article, I&rsquo;ll show only key snippets, sometimes simplifying the code a little bit, but you&rsquo;ll be able to checkout all the code in this <a href="https://github.com/glaforge/bluesky-topic-analysis">Github repository</a>.</p>
<p>As usual, the code will be in Java, and I&rsquo;m going to use my favorite Generative AI framework: <a href="https://docs.langchain4j.dev/">LangChain4j</a>.
For the large language model, my choice went for <a href="https://deepmind.google/technologies/gemini/">Gemini</a>,
and for the embedding model, I&rsquo;m calculating vectors thanks to Google Cloud Vertex AI <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-text-embeddings">embedding models</a>.
Clusters of messages will be created with the <a href="https://commons.apache.org/proper/commons-math/userguide/ml.html">Apache Commons Math</a> library.
The visualization will be implemented in JavaScript with the <a href="https://d3js.org/">D3.js</a> library.</p>
<h3 id="acquiring-bluesky-messages-via-websocket">Acquiring Bluesky messages via WebSocket</h3>
<p>Let&rsquo;s kick off the project by establishing a real-time connection to the Bluesky firehose using WebSockets, thanks to JDK 11&rsquo;s HTTP client.
This allows us to receive a constant stream of public posts as they happen.
The <code>liveMessages()</code> method manages the WebSocket connection and filters incoming messages based on language:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>httpClient.<span style="color:#4070a0">newWebSocketBuilder</span>().<span style="color:#4070a0">buildAsync</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>URI.<span style="color:#4070a0">create</span>(JETSTREAM_WS_ENDPOINT),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WebSocket.<span style="color:#4070a0">Listener</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>CompletionStage<span style="color:#666">&lt;?&gt;</span><span style="color:#bbb"> </span>onText(WebSocket<span style="color:#bbb"> </span>webSocket,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                    </span>CharSequence<span style="color:#bbb"> </span>data,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                    </span><span style="color:#902000">boolean</span><span style="color:#bbb"> </span>last)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// ... process incoming message ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>onText()</code> method within the <code>WebSocket.Listener</code> is our gateway to the firehose.
Each incoming message, received as a JSON string, needs to be parsed into a usable Java object.
Here&rsquo;s where Google&rsquo;s <code>Gson</code> library and Java <code>record</code>s come into play.
We&rsquo;ve defined a set of nested Java records that mirror the Bluesky message structure:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Message</span>(Commit<span style="color:#bbb"> </span>commit,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>did)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Commit</span>(Record<span style="color:#bbb"> </span>record,<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>cid)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Record</span>(String<span style="color:#bbb"> </span>text,<span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>langs,<span style="color:#bbb"> </span>Date<span style="color:#bbb"> </span>createdAt)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>These records give us a strongly typed way to access message data.
The Message record holds the actual post content (<code>text</code>), a list of languages (<code>langs</code>),
and the creation timestamp (<code>createdAt</code>), nested within <code>Commit</code> and <code>Record</code> records.
We use Gson to deserialize the JSON strings into these records:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Message<span style="color:#bbb"> </span>message<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GSON.<span style="color:#4070a0">fromJson</span>(String.<span style="color:#4070a0">valueOf</span>(text),<span style="color:#bbb"> </span>Message.<span style="color:#4070a0">class</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="calculating-vector-embeddings-for-all-the-messages">Calculating vector embeddings for all the messages</h3>
<p>To analyze the semantic similarity between posts, we convert each post&rsquo;s text into a numerical vector representation, or embedding.
This is achieved using a Vertex AI embedding model, via LangChain4j&rsquo;s <a href="https://docs.langchain4j.dev/integrations/embedding-models/google-vertex-ai/">Vertex AI module</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>EmbeddingModel<span style="color:#bbb"> </span>embeddingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">endpoint</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_VERTEXAI_ENDPOINT&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;text-embedding-005&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;re using <code>text-embedding-005</code> which is a good embedding model and understands multiple spoken languages
(which is important for analyzing posts coming from a hundred different spoken languages or so).</p>
<p>As embedding all messages takes a while, we&rsquo;re batching the calculation in parallel:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>allSegments<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>allMessages.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">map</span>(message<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>TextSegment.<span style="color:#4070a0">from</span>(message.<span style="color:#4070a0">commit</span>().<span style="color:#4070a0">record</span>().<span style="color:#4070a0">text</span>()))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toList</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Embedding<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>allEmbeddings<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>IntStream.<span style="color:#4070a0">range</span>(0,<span style="color:#bbb"> </span>numberOfParallelBatches)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">parallel</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">mapToObj</span>(i<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>embeddingModel.<span style="color:#4070a0">embedAll</span>(allSegments...)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">flatMap</span>(List::stream)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">toList</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="creating-clusters-of-posts">Creating clusters of posts</h3>
<p>With embeddings in hand, we can now group similar posts together using the <a href="https://en.wikipedia.org/wiki/DBSCAN">DBSCAN</a>
clustering algorithm (Density-based spatial clustering of applications with noise) :</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>clusters<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>DBSCANClusterer<span style="color:#666">&lt;</span>ClusterableEmbeddedMessage<span style="color:#666">&gt;</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>MAXIMUM_NEIGHBORHOOD_RADIUS,<span style="color:#bbb"> </span>MINIMUM_POINTS_PER_CLUSTER)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">cluster</span>(clusterableEmbeddedMessages);<span style="color:#bbb">
</span></span></span></code></pre></div><p>For 10k posts, using a minimum of 10 points per cluster sounds about right.
As a rule of thumb, I got good visualizations with one cluster point per 1k messages
(ie. 10 points per cluser for 10k messages, 20 points per cluster for 20k messages).</p>
<p>The maximum neighborhood radius at 0.5 also looked like a good value.
I tried smaller and bigger values, but either the cluster are too specific and narrow with low values,
or too broad and generalist with higher values.</p>
<p>It&rsquo;s important to check for yourself the <em>hyperparameters</em> of the algorithms you chose for your use case.
Some values might be better than others, and they are very much use-case dependant.
There&rsquo;s no magic numbers, you have to experiment to find the right mix for you!</p>
<p>Using a different embedding model (like <code>text-multilingual-embedding-002</code>), reducing the dimensionality to 128 dimensions,
I had to use a max neighborhood radius of 0.2 instead, to get a good number of clusters.</p>
<h3 id="generating-a-description-for-clusters-of-messages">Generating a description for clusters of messages</h3>
<p>At this point, we have topic clusters. But they&rsquo;re just bags of numbers without a real meaning for us, human beings.
What we need is a way to make sense of those clusters, to know what topic they cover.</p>
<p>We configure the Vertex AI Gemini model, thanks to LangChain4j&rsquo;s <a href="https://docs.langchain4j.dev/integrations/language-models/google-vertex-ai-gemini">Gemini module</a>, with a max number of tokens, to avoid situations where a topic description is too long:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>chatModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//  .modelName(&#34;gemini-2.0-flash-exp&#34;)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxOutputTokens</span>(25)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>You can use both the latest Gemini 1.5 Flash, or the new 2.0 Flash experimental model.
If you&rsquo;re hitting quota limits, as 2.0 is currently only in preview, 1.5 will give great results too.</p>
<p>To make the clusters more understandable, we call Gemini to generate a concise summary for each cluster,
passing all the messages contained in that cluster:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>modelResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatModel.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Summarize the following list of social media messages in one
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        simple description. Don&#39;t give a full sentence saying the
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        social messages are about a topic, just give the topic
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        directly in 10 words or less, without mentioning the
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        messages are social media posts or reactions.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(appendedMessages)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>When I was running this code on January 1st, I was seeing topics like <code>New Year's greetings and well wishes</code> or
<code>Happy New Year 2025 wishes and hopeful sentiments for the year</code>.
But some categories of topics often come back, like a big cluster of emojis expressing various expressions,
or people sharing video links on YouTube, or pictures from Instagram.
I also saw some interesting trends as they came up, like weather alerts for snow storms,
or someone famous receiving congratulations for announcing some anniversary.
There are also repeated posts tagging people to request funding for some cause.
Funnily, in the morning, I was often seeing people sharing in how many steps
they solved the <a href="https://www.nytimes.com/games/wordle/index.html">Wordle</a> word puzzle!</p>
<p>I filtered the messages to analyze only English messages for the purpose of this demo,
but there are a bunch of users setting their language as English, but posting in another language.
However it&rsquo;s not really a problem for Gemini which happily handles more than a hundred spoken languages.</p>
<h3 id="preparing-the-data-for-visualization">Preparing the data for visualization</h3>
<p>The cluster summaries and their sizes (number of posts) are then formatted as JSON data, for ingestion by D3.js:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> data <span style="color:#666">=</span> {
</span></span><span style="display:flex;"><span>  name<span style="color:#666">:</span> <span style="color:#4070a0">&#34;Bluesky topic clusters&#34;</span>,
</span></span><span style="display:flex;"><span>  children<span style="color:#666">:</span> [
</span></span><span style="display:flex;"><span>    {name<span style="color:#666">:</span> <span style="color:#4070a0">&#34;Summary of Cluster 1&#34;</span>, value<span style="color:#666">:</span> <span style="color:#40a070">396</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// ... other clusters
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>  ]
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>This JSON structure is ideal for consumption by D3.js, which we&rsquo;ll use for visualization.
The <code>FirehoseConsumer</code> class writes this JSON data to the <code>newdata.js</code> file,
which is integrated in the static web assets and loaded by D3.</p>
<h3 id="visualizing-the-data-with-d3js">Visualizing the data with D3.js</h3>
<p>Finally, the <code>visualisation.js</code> script uses D3.js to create an interactive bubble chart.
Each bubble represents a cluster, with its surface area corresponding to the number of posts in that cluster.
The color of the circles is also dynamically generated:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span><span style="color:#bbb"> </span>colorScale<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>d3.<span style="color:#4070a0">scaleQuantize</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">domain</span>(<span style="color:#666">[</span>0,<span style="color:#bbb"> </span>maxValue<span style="color:#666">]</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">range</span>(colorPalette);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">//.. later, in the circle</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">attr</span>(<span style="color:#4070a0">&#34;fill&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>d<span style="color:#bbb"> </span><span style="color:#666">=&gt;</span><span style="color:#bbb"> </span>d.<span style="color:#4070a0">children</span><span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span>colorScale(d.<span style="color:#4070a0">r</span>)<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#fefef0&#34;</span>)<span style="color:#bbb">
</span></span></span></code></pre></div><p>What&rsquo;s more interesting in this part of the project is how the visualization is created.
I was inspired by the circle packing visualization seen in this <a href="https://observablehq.com/@d3/pack/2">article</a>,
which uses D3.js&rsquo;s <a href="https://d3js.org/d3-hierarchy/pack">circle packing layout method</a>.
I borrowed heavily from this example, and tweaked it for my needs, and to my liking.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span><span style="color:#bbb"> </span>pack<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>d3.<span style="color:#4070a0">pack</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">size</span>(<span style="color:#666">[</span>width<span style="color:#bbb"> </span><span style="color:#666">-</span><span style="color:#bbb"> </span>margin<span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>2,<span style="color:#bbb"> </span>height<span style="color:#bbb"> </span><span style="color:#666">-</span><span style="color:#bbb"> </span>margin<span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span>2<span style="color:#666">]</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">padding</span>(4);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">const</span><span style="color:#bbb"> </span>root<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>pack(d3.<span style="color:#4070a0">hierarchy</span>(data)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">sum</span>(d<span style="color:#bbb"> </span><span style="color:#666">=&gt;</span><span style="color:#bbb"> </span>d.<span style="color:#4070a0">value</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">sort</span>((a,<span style="color:#bbb"> </span>b)<span style="color:#bbb"> </span><span style="color:#666">=&gt;</span><span style="color:#bbb"> </span>b.<span style="color:#4070a0">value</span><span style="color:#bbb"> </span><span style="color:#666">-</span><span style="color:#bbb"> </span>a.<span style="color:#4070a0">value</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>The tricky part, as well, was how to render and layout the text of the topics, along with the number of posts per cluster, inside each circle.
I got it working by appending a custom div, as a <em>foreign object</em> in the SVG document, and by tweaking the positioning:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>node.filter(d =&gt; <span style="color:#666">!</span>d.children)
</span></span><span style="display:flex;"><span>    .append(<span style="color:#4070a0">&#34;foreignObject&#34;</span>)
</span></span><span style="display:flex;"><span>    .attr(<span style="color:#4070a0">&#34;x&#34;</span>, d =&gt; <span style="color:#666">-</span><span style="color:#40a070">0.8</span> <span style="color:#666">*</span> d.r) <span style="color:#60a0b0;font-style:italic">// center horizontally
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    .attr(<span style="color:#4070a0">&#34;y&#34;</span>, d =&gt; <span style="color:#666">-</span><span style="color:#40a070">1.1</span><span style="color:#666">*</span>d.r) <span style="color:#60a0b0;font-style:italic">// center vertically, manually adjusted
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    .attr(<span style="color:#4070a0">&#34;width&#34;</span>, d =&gt; <span style="color:#40a070">1.6</span> <span style="color:#666">*</span> d.r)
</span></span><span style="display:flex;"><span>    .attr(<span style="color:#4070a0">&#34;height&#34;</span>, d =&gt; <span style="color:#40a070">2</span> <span style="color:#666">*</span> d.r)
</span></span><span style="display:flex;"><span>    .append(<span style="color:#4070a0">&#34;xhtml:div&#34;</span>)
</span></span><span style="display:flex;"><span>    .classed(<span style="color:#4070a0">&#34;foreignDiv&#34;</span>, <span style="color:#007020;font-weight:bold">true</span>)
</span></span><span style="display:flex;"><span>    .style(<span style="color:#4070a0">&#34;font-size&#34;</span>, d =&gt; d.r <span style="color:#666">/</span> <span style="color:#40a070">5.3</span> <span style="color:#666">+</span> <span style="color:#4070a0">&#34;px&#34;</span>) <span style="color:#60a0b0;font-style:italic">// dynamic font sizing
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    .html(d =&gt;
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;&lt;span style=&#39;font-size: &#34;</span> <span style="color:#666">+</span> (d.r <span style="color:#666">/</span> <span style="color:#40a070">2.5</span>) <span style="color:#666">+</span> <span style="color:#4070a0">&#34;px; color: &#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">+</span> d3.color(colorScale(d.r)).darker(<span style="color:#40a070">1</span>) <span style="color:#666">+</span> <span style="color:#4070a0">&#34;;&#39;&gt;&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">+</span> format(d.value)
</span></span><span style="display:flex;"><span>        <span style="color:#666">+</span> <span style="color:#4070a0">&#34;&lt;/span&gt;&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">+</span> d.data.name
</span></span><span style="display:flex;"><span>        <span style="color:#666">+</span> <span style="color:#4070a0">&#34;&lt;br/&gt;&#34;</span>
</span></span><span style="display:flex;"><span>    );
</span></span></code></pre></div><p>Lots of hard-coded values to make it look nice!</p>
<p>To put everything together: an HTML file imports D3.js, our <code>newdata.js</code> file containing the cluster definitions,
the <code>visualization.js</code> file creates the bubble chart, plus some CSS in <code>styles.css</code>.
And when running the Java class, the <code>newdata.js</code> is generated and updated in the <code>static</code> web asset folder.</p>
<h2 id="experiments-and-what-else-to-explore">Experiments, and what else to explore</h2>
<h3 id="no-live-demo-available">No live demo available</h3>
<p>Interesting topic visualizations happen when you have collected enough messages to analyze.
Gathering about 10 thousand posts seemed to offer good results, but in spite of the 10+ million users on Bluesky,
you still need about 4 or 5 minutes to store that many messages.
Without mentioning the time it takes to calculate the embeddings (about 30 seconds in parallel),
and the clustering algorithm (about 1 minute and a half with a runtime complexity of <code>n*log(n)</code>).
So this is not ideal for a <em>real-time</em> analysis of the current trending topics.
That&rsquo;s why I haven&rsquo;t posted a demo application online, as it&rsquo;s too slow to wait for the result to appear on screen.</p>
<p>What might be interesting to explore is somehow a live updating view that would be re-calculated every couple of minutes or so,
over a sliding window of messages, but the clustering duration is still a problem.
However, it&rsquo;s also something that could quickly become costly, considering the number of embedding calculations and generative summaries to generate each time.</p>
<h3 id="different-embedding-models">Different embedding models</h3>
<p>Before parallelizing / batching the vector embedding calculations (which still take half a minute),
I also tried a non-cloud hosted embedding model, like a quantized version of the
<a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">all-MiniLM-L6-v2</a> embedding model, which can run locally without a big GPU.
I used it in some other projects with success, but for this clustering exercise, I found the result of poor quality, as if it wasn&rsquo;t knowledgeable enough to discern different topics.</p>
<p>I paid attention to restricting the messages to only English messages, as I knew that that small model was more at ease with English, but that didn&rsquo;t really help.
Ideally, I&rsquo;d like to find a fast embedding model with good classification capabilities.
But read on, for another idea on speeding up the clustering part of the equation.</p>
<h3 id="different-clustering-algorithms">Different clustering algorithms</h3>
<p>DBSCAN isn&rsquo;t super fast, with a <code>n*log(n)</code> runtime complexity.</p>
<p>Apache Commons Math also offers a <a href="https://en.wikipedia.org/wiki/K-means%2B%2B">KMeans++</a> implementation that is faster (with a more linear runtime)
but the <code>k</code> hyperparameter to specify is always giving a fixed number of clusters.
One one hand, it&rsquo;s nice to have a more predictable visualization (neither too few, nor too many bubbles with small text to display),
on the other hand, the fact the number of clusters si set in stone, leads the clusters to be too generic and too broad,
and there&rsquo;s always one cluster that contains everything that couldn&rsquo;t be clustered in meaningful groups.</p>
<p>In spite of its runtime complexity, I like DBSCAN for the fact it creates quite diverse but acurate clusters,
as it figures itself how many clusters to create, depending on the various topics it&rsquo;ll come across.</p>
<p>There&rsquo;s another library that I&rsquo;d like to try some day, that&rsquo;s <a href="https://haifengl.github.io/clustering.html">Smile</a>.
It supports even more clustering algorithms than Apache Commons Math.</p>
<p>Something interesting going on for Smile is also its <a href="https://haifengl.github.io/manifold.html#t-sne">dimensionality reduction algorithms</a>
(that they call <em>manifold learning</em>) like <a href="https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding">t-SNE</a> and
<a href="https://en.wikipedia.org/wiki/Nonlinear_dimensionality_reduction#Uniform_manifold_approximation_and_projection">UMAP</a>.</p>
<p>Why am I mentioning dimensionality reduction?
For one, it&rsquo;s super handy for visualizing the clusters in 2D or 3D.
But another idea I wanted to try was that if the reduction is fast enough,
maybe applying the clustering algorithm on lower-dimensioned data would be much faster.
The <em>projection</em> (reducing the dimensionality) before clustering approach is also the one this <a href="https://github.com/huggingface/text-clustering">project from HuggingFace</a>
followed to cluster the <a href="https://huggingface.co/datasets/HuggingFaceTB/cosmopedia">Cosmopedia</a> dataset.</p>
<p>Indeed, Vertex AI embeddings generate vectors of 768 dimensions.
That said, some of the Vertex AI embeddings are <a href="https://huggingface.co/blog/matryoshka"><em>Matryoshka</em> embeddings</a>,
so we could also calculate clusters on truncated vectors, without losing too much accuracy, without even doing dimenstionality reduction!
Both <code>text-embedding-005</code> and <code>text-multilingual-embedding-002</code> support reducing the vector dimension, so it&rsquo;s worth trying.
You just need to set <code>outputDimensionality(128)</code> on the embedding model builder to reduce the dimensions down to 128.
Then the clustering time can be go down to 15 seconds instead of 80 seconds like with full 768-dimension vectors.</p>
<h3 id="what-else-to-try">What else to try?</h3>
<ul>
<li>In this experiment, I analyzed text, but users post hashtags, pictures, links, on their profiles.
It might be interesting to look at what is trending in terms of hashtags, or analyze the sentiment of messages related to such a hashtag.</li>
<li>Looking at links, maybe it&rsquo;d be interesting to also see what is shared, which news article is more popular&hellip;</li>
<li>Regarding pictures, we could perhaps see which animals are more trendy? And do some fun analysis of favorite animals in different countries&hellip;</li>
<li>Another interesting analysis could be to cluster user profiles, to find users posting on the same topics.</li>
<li>I&rsquo;d like to think more about how to make this application more lively, and make users explore indvidual posts contained in each clusters.</li>
</ul>
<p>Many more things to try out and explore!</p>
<h2 id="summary">Summary</h2>
<p>The generated visualization offers an intuitive and engaging way to explore the <strong>trending topics on Bluesky</strong>.
And <strong>generative AI</strong> tools like <strong>Gemini</strong> and <strong>Vertex AI</strong> are here to help creating such data explorations.</p>
<p>This project combines the power of <strong>real-time data streaming</strong>, <strong>AI-driven analysis</strong>, and (not-yet-interactive) <strong>visualization</strong> to provide a valuable tool for understanding the ever-evolving conversations on Bluesky.
It sets the stage for more sophisticated analysis, such as tracking topic evolution over time, sentiment analysis within clusters, and identification of key influencers within specific discussions.</p>
<p>As always, this project also confirmed that <strong>Java</strong> and <strong><a href="https://docs.langchain4j.dev/">LangChain4j</a></strong> are my two best buddies to explore topics with generative AI approaches (no need for Python!)
And I was happy to use <a href="https://d3js.org/">D3.js</a> again for visualization purposes.
It&rsquo;s not easy to master, but it&rsquo;s a super powerful library!
I&rsquo;m also glad that <a href="https://cloud.google.com/products/gemini/code-assist?e=0&amp;hl=en">Gemini Code Assist</a> helped me work with D3.js, to develop and enhance the visualization.</p>
<p>Finally, of course, the <strong>Gemini chat model</strong> and <strong>Vertex AI embedding model</strong> were perfect for the task, giving high quality embedding vectors, and clear synthetic summaries of social media posts.</p>
<p>Don&rsquo;t hesitate to <a href="https://github.com/glaforge/bluesky-topic-analysis">check out the code</a> and play with this project!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Let's think with Gemini Flash 2.0's experimental thinking mode and LangChain4j</title><link>https://glaforge.dev/posts/2024/12/20/lets-think-with-gemini-2-thinking-mode-and-langchain4j/</link><pubDate>Fri, 20 Dec 2024 16:14:21 +0100</pubDate><guid>https://glaforge.dev/posts/2024/12/20/lets-think-with-gemini-2-thinking-mode-and-langchain4j/</guid><description>&lt;p>Yesterday, Google released yet another cool Gemini model update, with &lt;strong>Gemini 2.0 Flash &lt;a href="https://ai.google.dev/gemini-api/docs/thinking-mode">thinking mode&lt;/a>&lt;/strong>.
Integrating natively and transparently some &lt;a href="https://www.promptingguide.ai/techniques/cot">chain of thought&lt;/a> techniques,
the model is able to take some more thinking time, and automatically decomposes a complex task into smaller steps, and explores various paths in its thinking process.
Thanks to this approach, Gemini 2.0 Flash is able to solve more complex problems than Gemini 1.5 Pro or the recent Gemini 2.0 Flash experiment.&lt;/p></description><content:encoded>
<![CDATA[<p>Yesterday, Google released yet another cool Gemini model update, with <strong>Gemini 2.0 Flash <a href="https://ai.google.dev/gemini-api/docs/thinking-mode">thinking mode</a></strong>.
Integrating natively and transparently some <a href="https://www.promptingguide.ai/techniques/cot">chain of thought</a> techniques,
the model is able to take some more thinking time, and automatically decomposes a complex task into smaller steps, and explores various paths in its thinking process.
Thanks to this approach, Gemini 2.0 Flash is able to solve more complex problems than Gemini 1.5 Pro or the recent Gemini 2.0 Flash experiment.</p>
<p><strong>And of course, you can already use Gemini 2.0 Flash Thinking with <a href="https://docs.langchain4j.dev/">LangChain4j</a>!</strong></p>
<p>So let&rsquo;s <em>delve</em> into some examples together!</p>
<h2 id="solving-a-riddle">Solving a riddle</h2>
<p>I found some <a href="https://parade.com/947956/parade/riddles/">riddles online</a> and thought I would try one with both the new Gemini 2.0 Flash model,
and the new thinking flavor as well, in order to contrast their way of working, and the outcome.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_AI_GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash-exp&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        The day before yesterday I was 21, and next year I will be 24.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When is my birthday?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini replies with:</p>
<pre tabindex="0"><code>This is a classic riddle! Here&#39;s how to solve it:

* **&#34;The day before yesterday I was 21&#34;** means yesterday you were 22.
* **&#34;Next year I will be 24&#34;** means you are currently 23.
* If you were 22 yesterday and 23 today, your birthday must be **today**.

Therefore, your birthday is **today**.
</code></pre><p>The answer is good, it&rsquo;s today, but it doesn&rsquo;t really tell exactly which date is the birthday.</p>
<p>Now let&rsquo;s update the model name to use the new thinking mode:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_AI_GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash-thinking-exp-1219&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        The day before yesterday I was 21, and next year I will be 24.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When is my birthday?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini advanced thinking process shows up:</p>
<pre tabindex="0"><code>Here&#39;s a thinking process to solve this riddle:

1. **Analyze the core statements:**
    * &#34;The day before yesterday I was 21.&#34;
    * &#34;Next year I will be 24.&#34;

2. **Break down the time references:**
Identify the different points in time being referenced.
    * &#34;The day before yesterday&#34; refers to a past date.
    * &#34;Yesterday&#34; refers to the day after &#34;the day before yesterday&#34;.
    * &#34;Today&#34; is the present day.
    * &#34;Tomorrow&#34; is the day after today.
    * &#34;Next year&#34; refers to the year after the current year.

3. **Assign ages to the time references:**  Based on the statements,
assign ages to the person at these different time points.
    * The day before yesterday: 21 years old.
    * Yesterday: Since the person&#39;s age changes by one each birthday,
     yesterday they were either still 21 or had just turned 22.
    * Today: Continuing the progression, today they are either 22
      or 23.
    * Next year: 24 years old.

4. **Focus on the age jump:**  The key is the jump in age from
&#34;today&#34; to &#34;next year.&#34; To go from being 22 or 23 today to being 24
next year *definitively* means their birthday must fall *between*
now and next year.

5. **Work backward from &#34;next year&#34;:** If they will be 24 next year,
they must turn 24 sometime within that year. This means they are
currently 23 *or* they are still 23 and about to turn 24 soon.

6. **Work forward from &#34;the day before yesterday&#34;:**
If they were 21 the day before yesterday, then yesterday they
turned 22. This means today they are 22.

7. **Reconcile the information:** We have two possibilities for the
current age: 22 (from the first statement) and potentially 23
(considering the jump to 24 next year). The only way both statements
can be true is if the birthday is *very* soon.

8. **Consider the implications of the age progression:**
    * If today they are 22, and next year they will be 24,
      the birthday must be *before* the end of the current year.
    * Since they were 21 the day before yesterday, they must have
      turned 22 *yesterday*.

9. **Determine the date:**  If they turned 22 yesterday,
and the statement refers to &#34;next year I will be 24,&#34; this can only
happen if today is the *last day* of the year, and their birthday
is *today*.

10. **Verify the solution:** Let&#39;s check if this works:
    * If today is December 31st:
        * The day before yesterday (December 29th) they were 21.
        * Yesterday (December 30th) they turned 22.
        * Today (December 31st) they are 22.
        * Next year (after December 31st),
          they will turn 23 and then 24. This works!

11. **Formalize the answer:** The birthday is on December 31st.

This is a classic riddle that plays on how we perceive time.
Here&#39;s how to solve it:

* **&#34;The day before yesterday I was 21&#34;**:
  This means yesterday you were 22.
* **&#34;Next year I will be 24&#34;**: This means you are currently 23.

For both of these statements to be true,
today must be **January 1st**.

Let&#39;s break it down:

* **January 1st:** You are 23.
* **December 31st (yesterday):** You were 22.
* **December 30th (the day before yesterday):** You were 21.
* **Sometime later this year (your birthday):** You will turn 24.

Therefore, your birthday is on **December 31st**.
</code></pre><p>Now, thanks to this advanced reasoning approach, it&rsquo;s able to tell me the exact date of the birthday.</p>
<p>I&rsquo;ve also tried the model&rsquo;s reasoning capabilities on coding problems (puzzles like Advent of Code ones),
and it&rsquo;s really doing a great job at solving most of them!</p>
<h2 id="multimodal-reasoning">Multimodal reasoning</h2>
<p>Remember that Gemini is a <strong>multimodal</strong> model?
That means it can accept other <em>modalities</em> like images, in addition to text.</p>
<p>So let&rsquo;s have a look at another example, with a word game from the
<a href="https://www.rd.com/article/visual-brain-teasers-and-puzzles/">Reader&rsquo;s digest</a>.
Only one of the rows is an anagram of a five-letter English word&hellip;</p>
<p><figure>
  <a href="#img-0196b85b7859d34b33551341b04bea49">
    <img src="/img/gemini/word-puzzle.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0196b85b7859d34b33551341b04bea49">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/word-puzzle.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>This time, it&rsquo;s a word game, but the information is inside an image.
So we need to pass both the image, and the problem statement.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_AI_GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash-thinking-exp-1219&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ImageContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>String(Base64.<span style="color:#4070a0">getEncoder</span>().<span style="color:#4070a0">encode</span>(readBytes(FILE_URI))),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;image/png&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Only one of the rows in the picture is an anagram of a
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            five-letter English word. Do you know which one it is?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>If I ask that puzzle to Gemini 2.0 Flash (by changing the model name used above), it&rsquo;ll tell me (quickly) that:</p>
<pre tabindex="0"><code>Certainly!
The anagram in the image is **O L C R E**,
which unscrambled is **COLOR**.
</code></pre><p>Gemini 2.0 Flash is at the very top of the LLM <a href="https://lmarena.ai/?leaderboard">leaderboard</a>
but it gets the answer wrong. Notice that <code>COLOR</code> is not an anagram of <code>OLCRE</code> at all.</p>
<p>Now let&rsquo;s see what the <em>thinking</em> model says:</p>
<pre tabindex="0"><code>The user wants me to find the row that is an anagram of a five-letter
English word. I need to go through each row and see if I can
rearrange the letters to form a valid five-letter word.

Row 1: L E G T N - Could be GENTL but need 5 letters. Could be GLENT,
but is not a common word.
Row 2: O L C R E - Could be CLORE, COLER, RECOL. RECOL is the most
promising one.
Row 3: T C O H E - Could be ECHOT.
Row 4: L U D I B - Could be BUILD.
Row 5: M E U D Q - No obvious combination.
Row 6: H I N W L - Could be WHILN.
Row 7: W E S O L - Could be LOWES, OWELS, SOWEL, WOLES.

From the analysis, RECOL seems to be a valid word and has 5 letters.
Let me double check other possibilities.
Row 1: L E G T N -  TENGLE? No.
Row 3: T C O H E -  Could be CHETO, but not a common word.
Row 4: L U D I B -  Could be BLUID, but not a common word.
Row 6: H I N W L -  Could be WHILE.

Out of the possible options, WHILE and RECOL are valid five-letter
words. Let me re-examine the instruction &#34;Only one of the rows in
the picture is an anagram of a five-letter English word.&#34;

Row 1: L E G T N - No obvious five-letter word.
Row 2: O L C R E - CLORE, COLER, RECOL
Row 3: T C O H E - ECHOT
Row 4: L U D I B - BUILD
Row 5: M E U D Q - No obvious five-letter word.
Row 6: H I N W L - WHILE
Row 7: W E S O L - LOWES, OWELS, SOWEL, WOLES

There seems to be more than one. Let me re-check the letter counts.
 All rows have 5 letters.

Rechecking my anagram findings:
Row 2: O L C R E -  Could form RECOL
Row 3: T C O H E - Could form ECHOT
Row 4: L U D I B - Could form BUILD
Row 6: H I N W L - Could form WHILE
Row 7: W E S O L - Could form LOWES

Let me think again. The instruction says *only one*. I might be making
a mistake in identifying a valid word.

Re-examining:
Row 1: L E G T N - No common five-letter word.
Row 2: O L C R E - RECOL
Row 3: T C O H E - ECHOT
Row 4: L U D I B - BUILD
Row 5: M E U D Q - No common five-letter word.
Row 6: H I N W L - WHILE
Row 7: W E S O L - LOWES

Perhaps some of these are not valid English words.
Let me verify the words.
RECOL -  Yes, it&#39;s a valid word.
ECHOT - No, ECHO is a word, ECHOT is not.
BUILD - Yes, it&#39;s a valid word.
WHILE - Yes, it&#39;s a valid word.
LOWES - Yes, it&#39;s a valid word.

I am still finding more than one valid word. Let me go through each
row very carefully again.

Row 1: L E G T N - No obvious 5-letter word
Row 2: O L C R E - Could be CLORE, COLER, RECOL.
Row 3: T C O H E - Could be CHOTE. No such word.
Row 4: L U D I B - Could be BUILD.
Row 5: M E U D Q - No obvious 5-letter word.
Row 6: H I N W L - Could be WHILE.
Row 7: W E S O L - Could be LOWES.

Let me verify the validity of each potential word.
RECOL - Valid English word.
BUILD - Valid English word.
WHILE - Valid English word.
LOWES - Valid English word (name of a store).

I am still struggling to find only one. Perhaps I am overlooking
something simple. Let me reread the question carefully.
&#34;Only one of the rows in the picture is an anagram of a five-letter
English word.&#34;

Let me try rearranging the letters systematically for each row.

Row 1: L E G T N -  No five letter word.
Row 2: O L C R E -  RECOL
Row 3: T C O H E - No five letter word.
Row 4: L U D I B -  BUILD
Row 5: M E U D Q - No five letter word.
Row 6: H I N W L -  WHILE
Row 7: W E S O L -  LOWES

I am still identifying multiple valid words. Could there be a
constraint I am missing? The hints are just crops, so they don&#39;t
add new information.

Let me consider uncommon words.

Row 2: O L C R E -  Could be ORCEL.
Row 3: T C O H E -
Row 7: W E S O L - Could be OLWES.

I am still finding multiple possibilities. The question states
*only one*. This implies that my initial assessment of multiple
possibilities is incorrect. Let me re-examine each row with more
scrutiny for common five-letter words.

Row 1: L E G T N - No.
Row 2: O L C R E - RECOL.
Row 3: T C O H E - No.
Row 4: L U D I B - BUILD.
Row 5: M E U D Q - No.
Row 6: H I N W L - WHILE.
Row 7: W E S O L - LOWES.

I have identified four potential candidates. There must be something
I am not considering. Is there a specific type of five-letter word?
No constraint mentioned.

Let me check my understanding of anagram. An anagram is a
rearrangement of letters to form a new word.

Perhaps some of the words I identified are not standard English words.

RECOL - Yes, it is.
BUILD - Yes, it is.
WHILE - Yes, it is.
LOWES - Yes, it is.

I am still at a loss. The instruction is very clear: &#34;Only one of the
rows... is an anagram of a five-letter English word.&#34;

Let me try a different approach. Assume only one is correct
and try to disprove the others.

If Row 2 (O L C R E - RECOL) is the answer, then none of the others
should form a valid five-letter word.
Row 4 (L U D I B) - BUILD is a valid word. This contradicts the
&#34;only one&#34; rule.

If Row 4 (L U D I B - BUILD) is the answer, then none of the others
should form a valid five-letter word.
Row 2 (O L C R E) - RECOL is a valid word. This contradicts the
&#34;only one&#34; rule.

If Row 6 (H I N W L - WHILE) is the answer, then none of the others
should form a valid five-letter word.
Row 2 (O L C R E) - RECOL is a valid word. This contradicts the
&#34;only one&#34; rule.

If Row 7 (W E S O L - LOWES) is the answer, then none of the others
should form a valid five-letter word.
Row 2 (O L C R E) - RECOL is a valid word. This contradicts the
&#34;only one&#34; rule.

There must be something very obvious I am missing.

Let me read the rows aloud and see if something jumps out.
L E G T N
O L C R E
T C O H E
L U D I B
M E U D Q
H I N W L
W E S O L

Is it possible that some of the letter combinations seem like a word
but aren&#39;t actually?

Let&#39;s focus on making sure each identified word is definitively
correct.

RECOL - Define: To collect again. Yes, it&#39;s a valid word.
BUILD - Define: To construct. Yes, it&#39;s a valid word.
WHILE - Define: A period of time. Yes, it&#39;s a valid word.
LOWES -  While primarily known as a store name, it can be
a plural noun.
However, the question is about anagrams of five-letter words.

Let&#39;s go back to each row and try to form words methodically.

Row 1: L E G T N -  Tingle, Longe, Glent. None are common five-letter
words.
Row 2: O L C R E -  Clore, Coler, Lorec, Recol. RECOL is a valid word.
Row 3: T C O H E -  Chote, Cothe, EchoT. None are common five-letter
words.
Row 4: L U D I B -  Bluid, Build. BUILD is a valid word.
Row 5: M E U D Q - No valid word.
Row 6: H I N W L -  Whiln, While. WHILE is a valid word.
Row 7: W E S O L -  Lowes, Owels, Sowle, Woles. LOWES is a valid word.

The problem states *only one*. This is the key.
There must be something about one of these words that makes it
the correct answer over the others.

Could it be about the commonality of the word?  While all are valid,
some are more frequently used.

RECOL - Less common.
BUILD - Very common.
WHILE - Very common.
LOWES - Common (as a store name).

Could there be a nuance in the definition or usage?

Let me consider if the capitalization or the &#34;RD&#34; at the bottom has
any relevance (unlikely for an anagram puzzle).

The crops are there to help see better, so the visual aspect
is important.

Let me look at the letters themselves and see if there&#39;s a pattern
or something I&#39;ve missed.

Maybe one of the identified words isn&#39;t as standard as the others
in a pure linguistic sense.

Consider &#34;LOWES&#34;. It&#39;s primarily a proper noun. Is the puzzle looking
for common nouns or verbs?  The question says &#34;a five-letter English
word,&#34; which is quite broad.

Let me revisit the less common words I dismissed earlier.

Row 1: L E G T N - GLENT (a small secluded valley).
Row 3: T C O H E -  None.
Row 6: H I N W L -  WHILN (the pod or husk of seeds).

If GLENT is considered, we have another possibility.
If WHILN is considered, another possibility.

The constraint &#34;only one&#34; is crucial.

Let&#39;s assume, for a moment, that the answer is indeed one of the ones
I identified and I need to find a reason to exclude the others.

Could the structure of the letter arrangement provide a clue?
No obvious pattern.

Let me focus on the most common five-letter words from my list:
BUILD and WHILE. If one of these is the answer, why isn&#39;t the other?

Perhaps there&#39;s a very simple word I&#39;m overlooking in one of the rows.

Row 3: T C O H E -  THROE.

THROE is a valid five-letter word.

Now my list is: RECOL, BUILD, WHILE, LOWES, THROE.
Still more than one.

I am stuck on the &#34;only one&#34; constraint. What could make one row
uniquely solvable?

Could it be about pronunciation or homophones?
Unlikely for an anagram.

Let me go back to the instructions and the image.
There are no additional instructions.

Is it possible there&#39;s a very obscure five-letter English word?

Let&#39;s review each row and try all possible combinations.
This will be tedious, but the &#34;only one&#34; constraint suggests
a unique solution.

Row 1: L E G T N -  ELTNG, ELNTG, ETLNG, ETNLG, ENLTG, ENTLG...
This is not efficient.

Let me rely on my vocabulary for common words first.

Consider the possibility that one of the words I identified isn&#39;t
a standard English word in the context of this puzzle.

Could &#34;LOWES&#34; as a store name not count? But the letters
can form the word.

Let&#39;s try working backward from the assumption that only one row
is correct. If I pick a row, can I definitively say the others
don&#39;t form any five-letter words?

If Row 2 (O L C R E) is the answer (RECOL), then the others cannot
form a valid five-letter word. But Row 4 (BUILD), Row 6 (WHILE),
and Row 7 (LOWES) do.

There must be a constraint I am not understanding
or a word I am misinterpreting.

Could it be about the frequency of letter usage in the
English language? Unlikely.

Let me consider common knowledge or general vocabulary.

Perhaps one of the letter sets can *only* form one five-letter word,
while others can form multiple.

Row 2: O L C R E - RECOL, CLORE, COLER.
Row 4: L U D I B - BUILD. Only one common word.
Row 6: H I N W L - WHILE. Only one common word.
Row 7: W E S O L - LOWES, OWELS, SOWLE, WOLES.

This is a potential lead. Row 4 and Row 6 seem to have a single,
very common five-letter word.

Let me double-check Row 4 (L U D I B). Are there other common
five-letter words?  BLUID is not common.

Let me double-check Row 6 (H I N W L). Are there other common
five-letter words? WHILN is less common.

The wording &#34;anagram of a five-letter English word&#34; is precise.

Could it be that some of the words I found are plurals or other forms
that don&#39;t fit the strict definition?  No, RECOL, BUILD, WHILE
are base forms.

Let me consider the visual arrangement again.
Is there any hidden message or pattern?  The lines separate the rows.

The &#34;RD&#34; in the corner is likely just a watermark.

The color is uniform.

Back to the anagrams. The &#34;only one&#34; is the key.

If I had to pick one, which row is *unambiguously*
a single five-letter English word?

Row 4: L U D I B - BUILD. It&#39;s hard to make another common
five-letter word from these letters.

Let&#39;s confirm this. Consider permutations.
BLUID is the only other plausible one, and it&#39;s not common.

Could the answer be BUILD based on this uniqueness?

Let me test this hypothesis against the other rows.

Row 2: O L C R E - RECOL, CLORE, COLER. Multiple options.
Row 6: H I N W L - WHILE. WHILN is another possibility,
though less common.
Row 7: W E S O L - LOWES, OWELS, etc. Multiple options.

This line of reasoning seems promising.
The &#34;only one&#34; could refer to the row that forms *only one*
common five-letter English word.

If this is the case, Row 4 (L U D I B - BUILD)
fits this criterion best.


Solution: The row that is an anagram of a five-letter English word
is **LUDIB**, which can be rearranged to form the word **BUILD**.

While some other rows can form five-letter words (such as RECOL
from OLCRE, WHILE from HINWL, and LOWES from WESOL), the question
states that &#34;only one of the rows... is an anagram of a five-letter
English word.&#34;  Considering the commonality and unambiguous nature
of the resulting word, &#34;BUILD&#34; from &#34;LUDIB&#34; stands out as the most
likely single correct answer. Other rows could potentially form
multiple valid five-letter words, making &#34;LUDIB&#34; unique in its most
obvious and common anagram.
</code></pre><p>That&rsquo;s a pretty long answer, but it&rsquo;s correct!</p>
<p>The answer actually concatenates both the long thinking process, and at the end, the simpler answer deduced from that thinking process.
With a version of the API endpoint that distinguishes clearly both thinking and answer parts, and with an update to the LangChain4j framework,
we&rsquo;ll make it configurable to return or not the thinking steps, in case you want to just return the final answer.</p>
<h2 id="thinking-about-it">Thinking about it&hellip;</h2>
<p>This is very interesting to see the thinking process the model follows when reasoning about the problem.
Compared to some competing model which hides its thoughts, Gemini shares everything it went through.
And it does it faster!</p>
<p>You would likely not use a reasoning model for each and every question you could ask an LLM, obviously.
However, for solving advanced problems that require a deeper thought process, this is definitely the way to go!</p>
<p>And of course, I&rsquo;m happy that <a href="https://docs.langchain4j.dev/">LangChain4j</a> lets me play with this new model out of the box!
If you don&rsquo;t feel like coding right away in Java, you can also play with the model in <a href="https://aistudio.google.com/app/prompts/new_chat">Google AI Studio</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Detecting objects with Gemini 2.0 and LangChain4j</title><link>https://glaforge.dev/posts/2024/12/13/detecting-objects-with-gemini-2-and-langchain4j/</link><pubDate>Fri, 13 Dec 2024 17:54:32 +0100</pubDate><guid>https://glaforge.dev/posts/2024/12/13/detecting-objects-with-gemini-2-and-langchain4j/</guid><description>&lt;p>Hot on the heels of the &lt;a href="https://blog.google/technology/google-deepmind/google-gemini-ai-update-december-2024/">announcement of Gemini 2.0&lt;/a>,
I played with the new experimental model both from within &lt;a href="https://aistudio.google.com/app/prompts/new_chat">Google AI Studio&lt;/a>,
and with &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>.&lt;/p>
&lt;p>Google released Gemini 2.0 Flash, with new modalities, including interleaving images, audio, text, video, both in input and output.
Even a live bidirectional speech-to-speech mode, which is really exciting!&lt;/p>
&lt;p>When experimenting with AI Studio, what attracted my attention was AI Studio&amp;rsquo;s new &lt;a href="https://aistudio.google.com/starter-apps">starter apps&lt;/a> section.
There are 3 examples (including links to Github projects showing how they were implemented):&lt;/p></description><content:encoded>
<![CDATA[<p>Hot on the heels of the <a href="https://blog.google/technology/google-deepmind/google-gemini-ai-update-december-2024/">announcement of Gemini 2.0</a>,
I played with the new experimental model both from within <a href="https://aistudio.google.com/app/prompts/new_chat">Google AI Studio</a>,
and with <a href="https://docs.langchain4j.dev/">LangChain4j</a>.</p>
<p>Google released Gemini 2.0 Flash, with new modalities, including interleaving images, audio, text, video, both in input and output.
Even a live bidirectional speech-to-speech mode, which is really exciting!</p>
<p>When experimenting with AI Studio, what attracted my attention was AI Studio&rsquo;s new <a href="https://aistudio.google.com/starter-apps">starter apps</a> section.
There are 3 examples (including links to Github projects showing how they were implemented):</p>
<ul>
<li><strong>spatial understanding</strong> — get Gemini to recognize objects in pictures, and give you bounding boxes for those objects</li>
<li><strong>video analyzer</strong> — to summarize, describe scenes, extract texts and objects from videos</li>
<li><strong>map explorer</strong> — an integration with Google Maps to explore the world</li>
</ul>
<p>The first one, on detecting objects, reminded me of an old demo of mine I had developed with Gemini 1.0 Pro Vision to recognise the cards of the Skyjo card game
(a fun little card game I&rsquo;ve been playing a lot with my youngest daughter):</p>
<p><figure>
  <a href="#img-f1f4c9f7634712cce6c90857fb574fd9">
    <img src="/img/gemini/skyjo-ai-studio.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f1f4c9f7634712cce6c90857fb574fd9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/skyjo-ai-studio.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>If you look at the screenshot above, you&rsquo;ll see some prompt suggestions to get bounding boxes around detected objects.
You&rsquo;ll notice that the model seems pretty capable at recnogising the numbers on those cards.
And with some bits of prompt engineering, it ignores cards facing down (attribute a value of 0 for those cards).
In the end, you can sum up all the points, and have the current score for your cards.</p>
<p>Back in the day, Gemini 1.0 was making quite a few mistakes when detecting and recognising the values of the cards,
in particular when the cards were tilted, or upside down.
But Gemini 2.0 Flash has greatly improved, and is much more capable.</p>
<p>So I decided to see:</p>
<ul>
<li>if LangChain4j works well with Gemini 2.0 Flash,</li>
<li>and if I can craft a prompt that detects my cards flawlessly.</li>
</ul>
<p>And I&rsquo;m glad to report that for all the photos I had taken of my games (14 pictures), I managed to score a 100% score of recognition.
Of course, LangChain4j is happy to call Gemini 2 without a problem (although we&rsquo;ll have to update the framework with the new modalities when a Java SDK is made available)</p>
<h2 id="lets-code">Let&rsquo;s code!</h2>
<p>I&rsquo;ll skip some of the boilerplate code to iterate over all my test pictures, properly labeled with the card values.
But you can have a look at this <a href="https://gist.github.com/glaforge/d6e845c673a5441823efc800d2d6bbf6">gist</a> with all the code.</p>
<p>First, let&rsquo;s create some Java <code>record</code>s to represent the cards, their bounding box, and number labels:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Card</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>label,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>BoundingBox<span style="color:#bbb"> </span>boundingBox<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">BoundingBox</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>x1,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>y1,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>x2,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>y2)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;ll use GSON for marshalling/unmarshalling those card details.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-2.0-flash-exp&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(SchemaHelper.<span style="color:#4070a0">fromClass</span>(Card<span style="color:#666">[]</span>.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">temperature</span>(0.<span style="color:#4070a0">1f</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice that we&rsquo;re using the new model: <code>gemini-2.0-flash-exp</code> (it&rsquo;s labeled <em>experimental</em> for now).
And also pay attention to the response MIME type, which is JSON, and the fact we&rsquo;re defining a response schema:
We instruct Gemini to return a valid JSON object whose schema corresponds to the <code>record</code>s we&rsquo;ve just defined.</p>
<p>Next, let&rsquo;s load all the cards pictures and details (our sample dataset):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>cardsExamples<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>processImageFiles<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;skyjo-counter/samples&#34;</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now we can iterate over all the cards, to check that Gemini 2 recognises all of them:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(CardsExample<span style="color:#bbb"> </span>example<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>cardsExamples)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;File: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>example.<span style="color:#4070a0">imageFile</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s look at the convoluted prompt I came up with to ensure to recognise all my sample pictures:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      Detect playing cards with numbers, with no more than 12 items.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      Output a JSON list of cards, where each entry contains the 2D
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      bounding box in `boundingBox` and the `label` is the big number
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      displayed in the center of the card.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      If you see the text &#34;SKYJO&#34; on the card, use 0 as the label
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      in `label`.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      Ignore the small numbers in the corners of the cards.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      Ignore cards with text written on them.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      Be careful when reading the numbers, as sometimes some cards
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      are tilted, cut, or upside down.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ImageContent.<span style="color:#4070a0">from</span>(example.<span style="color:#4070a0">imageFile</span>().<span style="color:#4070a0">toUri</span>()),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Detect the cards of this image.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>We give Gemini some system instructions to pay attention to the (maximum 12) cards numbers,
to return bounding boxes around the detected cards, and to give the big number at the center of the cards as the label.
There are some extra instructions for cards upside down, to ignore the small numbers in the corners, or to pay attention to the fact some cards may be cut, tilted, etc.
This prompt may not necessarily perfect, but at least it worked for all my pictures!</p>
<p>Then, as user message, we pass both the picture, and the request to detect the cards in the picture.</p>
<p>Last step, let&rsquo;s parse the JSON returned structure with GSON
(I could have used LangChain4j&rsquo;s <code>AiServices</code> for a cleaner and more type-safe approach),
and we&rsquo;re counting the points.
If the sum of points isn&rsquo;t correct, we display the cards that have been recognised, for troubleshooting purpose.</p>
<p>Let&rsquo;s check the output:</p>
<pre tabindex="0"><code>File: skyjo-counter/samples/-1 -1 -2 0 3 0 4.jpg
 ==&gt; Your points: 3
File: skyjo-counter/samples/1 4 1 -1 3 0 0 3 3 3.jpg
 ==&gt; Your points: 17
File: skyjo-counter/samples/3 9 3 4 5 2 4 5.jpg
 ==&gt; Your points: 35
File: skyjo-counter/samples/3 5 2 4 5.jpg
 ==&gt; Your points: 19
File: skyjo-counter/samples/-1 4.jpg
 ==&gt; Your points: 3
File: skyjo-counter/samples/1 0 2.jpg
 ==&gt; Your points: 3
File: skyjo-counter/samples/1 0 3 4 0 3 1 -1 2.jpg
 ==&gt; Your points: 13
File: skyjo-counter/samples/4 4 1 2 0 2 1 2 3.jpg
 ==&gt; Your points: 19
File: skyjo-counter/samples/0 -1 -1 -2 0 0 0 0 -1.jpg
 ==&gt; Your points: -5
File: skyjo-counter/samples/4 1 -2 2 4 2 3 3 0 5.jpg
 ==&gt; Your points: 22
File: skyjo-counter/samples/4 3 0 -2 -1 -1 2 1 3.jpg
 ==&gt; Your points: 9
File: skyjo-counter/samples/6 1 2 6 1 3.jpg
 ==&gt; Your points: 19
File: skyjo-counter/samples/3 3 5 2 5.jpg
 ==&gt; Your points: 18
File: skyjo-counter/samples/1 -2 5 2 -1 8 0.jpg
 ==&gt; Your points: 13
</code></pre><p>The picture file names contain the values of the cards, so it was easy to check for the ground truth!
And if we sum up those numbers, we should come up with the same number of points.</p>
<h2 id="now-what">Now what?</h2>
<p>Well, first of all, I&rsquo;m happy that LangChain4j works with Gemini 2.0!
Secondly, that the quality of object detection keeps on progressing nicely.
Thirdly, I might have to update my old demo, to make it a PWA app that could run on mobile, so that I don&rsquo;t have to count the sum of the card numbers in my head, because I&rsquo;m lazy!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Semantic code search for Programming Idioms with LangChain4j and Vertex AI embedding models</title><link>https://glaforge.dev/posts/2024/12/02/semantic-code-search-for-programming-idioms-with-langchain4j-and-vertex-ai-embedding-models/</link><pubDate>Mon, 02 Dec 2024 14:42:02 +0100</pubDate><guid>https://glaforge.dev/posts/2024/12/02/semantic-code-search-for-programming-idioms-with-langchain4j-and-vertex-ai-embedding-models/</guid><description>&lt;p>&lt;em>By Guillaume Laforge &amp;amp; Valentin Deleplace&lt;/em>&lt;/p>
&lt;p>The &lt;a href="https://programming-idioms.org/coverage">Programming Idioms&lt;/a> community website created by &lt;a href="https://www.linkedin.com/in/deleplacevalentin/">Valentin&lt;/a> lets developers share typical implementations in various programming languages for usual tasks like printing the famous “Hello World!” message, counting the characters in a string, sorting collections, or formatting dates, to name a few. And many more: there are currently 350 idioms, covering 32 programming languages. It’s a nice way to discover how various languages implement such common tasks!&lt;/p></description><content:encoded>
<![CDATA[<p><em>By Guillaume Laforge &amp; Valentin Deleplace</em></p>
<p>The <a href="https://programming-idioms.org/coverage">Programming Idioms</a> community website created by <a href="https://www.linkedin.com/in/deleplacevalentin/">Valentin</a> lets developers share typical implementations in various programming languages for usual tasks like printing the famous “Hello World!” message, counting the characters in a string, sorting collections, or formatting dates, to name a few. And many more: there are currently 350 idioms, covering 32 programming languages. It’s a nice way to discover how various languages implement such common tasks!</p>
<p>The website features a typical keyword-based search feature, which is able to search through idiom descriptions, source code, comments, and tags. However, we (Guillaume &amp; Valentin) were curious to see if we could enhance the search with a more semantic focus, taking advantage of Vertex AI <strong>embedding models</strong>, and their ability to <strong>search through code from natural language queries</strong>. With a semantic search, you’re not limited to results that match some keywords from a query, but you’ll get results even when using synonyms, or descriptions of what the code is doing.</p>
<p>Embedding models take a string in input, and generate a multidimensional floating point vector representation of that string. What’s interesting with those vectors is that input strings whose vectors are close to each other (for instance via a <a href="https://en.wikipedia.org/wiki/Cosine_similarity">cosine similarity</a> calculation) are generally close to each other semantically speaking as well. This is why you can create <strong>semantic searches</strong>: you can search for semantically similar strings, even if they don’t share the same keywords and use synonyms instead. You can explore Guillaume’s article “<a href="https://glaforge.dev/posts/2024/07/02/the-power-of-embeddings-how-numbers-unlock-the-meaning-of-data/">The power of embeddings: How numbers unlock the meaning of data</a>” to learn more about embedding models.</p>
<p>In the code shown in this article, we’ll be coding in Java, and we will be using the <a href="https://docs.langchain4j.dev/">LangChain4j</a> open source framework. You can view the <a href="https://gist.github.com/glaforge/4e45fa4222dd803d6d8bbf2b9335e90d">full source code in this gist</a>, and below, we’ll highlight the key elements of this program.</p>
<p>We’ll be using the latest version of Google Cloud Vertex AI embedding models: <code>text-embedding-005</code>. Why is it important? Because this new version supports a new <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-text-embeddings">task type</a>: <code>CODE_RETRIEVAL_QUERY</code>.</p>
<p>With this embedding model, there are different task types that optimize the embedding of text for different purposes, like for document retrieval, question &amp; answering, fact verification… and now for code retrieval queries. <strong>With this code retrieval query task type, you can search for code snippets with natural language</strong>! This will come in handy for us when we want to do natural language semantic search throughout our programming idioms!</p>
<p>Before implementing our smart code search, if you want to learn more about the new task types of our embedding models, please go check this video:</p>
<p><em>New &ldquo;task type&rdquo; embedding from the DeepMind team improves RAG search quality</em>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/BgfSCTdlvAA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="lets-collect-the-idioms">Let’s collect the idioms</h2>
<p>The <a href="https://programming-idioms.org/">Programming Idioms</a> website exposes a simple REST API. An endpoint allows you to get all the idioms in one HTTP GET call, but you can also access individual idioms via another GET request:</p>
<ul>
<li><a href="https://programming-idioms.org/api/idioms/all">https://programming-idioms.org/api/idioms/all</a> — Lists all the idioms</li>
<li><a href="https://programming-idioms.org/api/idiom/202">https://programming-idioms.org/api/idiom/202</a> — A single idiom identified by its ID</li>
</ul>
<p>Idioms contain various fields like their title, description, keywords, and provide one or more implementations in various programming languages.</p>
<p>For example, the “Sum of squares” idiom starts like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="">Id:</span> <span style="">202,</span>
</span></span><span style="display:flex;"><span>  <span style="">OrigId:</span> <span style="">0,</span>
</span></span><span style="display:flex;"><span>  <span style="">Title:</span> <span style="color:#062873;font-weight:bold">&#34;Sum of squares&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">LeadParagraph:</span> <span style="color:#062873;font-weight:bold">&#34;Calculate the sum of squares _s of _data, an array of floating point values.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">ExtraKeywords:</span> <span style="color:#062873;font-weight:bold">&#34;reduce&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">Author:</span> <span style="color:#062873;font-weight:bold">&#34;Bart&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">CreationDate:</span> <span style="color:#062873;font-weight:bold">&#34;2019-09-28T20:37:11.726064Z&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">LastEditor:</span> <span style="color:#062873;font-weight:bold">&#34;programming-idioms.org&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">EditSummary:</span> <span style="color:#062873;font-weight:bold">&#34;New Java implementation by user [reilas]&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">LastEditedImplID:</span> <span style="">6839,</span>
</span></span><span style="display:flex;"><span>  <span style="">OriginalAttributionURL:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">Picture:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">ImageURL:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">ImageWidth:</span> <span style="">0,</span>
</span></span><span style="display:flex;"><span>  <span style="">ImageHeight:</span> <span style="">0,</span>
</span></span><span style="display:flex;"><span>  <span style="">ImageAlt:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">Version:</span> <span style="">40,</span>
</span></span><span style="display:flex;"><span>  <span style="">VersionDate:</span> <span style="color:#062873;font-weight:bold">&#34;2024-11-08T22:54:02.691646Z&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="">Implementations:</span>
</span></span><span style="display:flex;"><span>  <span style="">[</span>
</span></span><span style="display:flex;"><span>    <span style="">{</span>
</span></span><span style="display:flex;"><span>      <span style="">Id:</span> <span style="">3466,</span>
</span></span><span style="display:flex;"><span>      <span style="">OrigId:</span> <span style="">-1,</span>
</span></span><span style="display:flex;"><span>      <span style="">Author:</span> <span style="color:#062873;font-weight:bold">&#34;Bart&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">CreationDate:</span> <span style="color:#062873;font-weight:bold">&#34;2019-09-28T20:37:11.726064Z&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">LastEditor:</span> <span style="color:#062873;font-weight:bold">&#34;programming-idioms.org&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">LanguageName:</span> <span style="color:#062873;font-weight:bold">&#34;Pascal&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">CodeBlock:</span> <span style="color:#062873;font-weight:bold">&#34;var
</span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">  data: array of double;
</span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">...
</span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">  s := SumOfSquares(data);
</span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">...&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">OriginalAttributionURL:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">DemoURL:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">DocumentationURL:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">AuthorComment:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">Version:</span> <span style="">2,</span>
</span></span><span style="display:flex;"><span>      <span style="">VersionDate:</span> <span style="color:#062873;font-weight:bold">&#34;2021-12-07T10:07:15.952746Z&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">Rating:</span> <span style="">0,</span>
</span></span><span style="display:flex;"><span>      <span style="">Checked:</span> <span style="">false,</span>
</span></span><span style="display:flex;"><span>      <span style="">ImportsBlock:</span> <span style="color:#062873;font-weight:bold">&#34;uses math;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">PictureURL:</span> <span style="color:#062873;font-weight:bold">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="">Protected:</span> <span style="">false</span>
</span></span><span style="display:flex;"><span>    }<span style="">,</span>
</span></span><span style="display:flex;"><span>    <span style="">…</span>
</span></span><span style="display:flex;"><span>  <span style="">]</span>
</span></span><span style="display:flex;"><span><span style="">}</span>
</span></span></code></pre></div><p>What’s interesting for us, for a semantic code search engine, are the following idiom fields:</p>
<ul>
<li><code>Id</code> — the unique ID of the idiom</li>
<li><code>Title</code> — that describes the idiom in a short way</li>
<li><code>LeadParagraph</code> — which is a more detailed definition of the idiom</li>
<li>ExtraKeywords — words related to the idiom, for search</li>
</ul>
<p>And for the implementations, the fields:</p>
<ul>
<li><code>Id</code> — the unique ID of the idiom implementation</li>
<li><code>CodeBlock</code> — which contains the source code of the implemented idiom</li>
<li><code>LanguageName</code> — which says which programming language was used for that implementation</li>
<li>AuthorComment — a small explanation about the implementation</li>
</ul>
<p>We can represent those two notions, idiom &amp; implementations, as Java records:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Idiom</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;Id&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">long</span><span style="color:#bbb"> </span>id,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;Title&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>title,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;LeadParagraph&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>description,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;ExtraKeywords&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>keywords,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;Implementations&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Implementation<span style="color:#666">[]</span><span style="color:#bbb"> </span>implementations<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Implementation</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;Id&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#902000">long</span><span style="color:#bbb"> </span>id,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;LanguageName&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>language,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;CodeBlock&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>code,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@SerializedName</span>(<span style="color:#4070a0">&#34;AuthorComment&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>comment<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The annotations are here to map between the JSON key names and the Java record field names.</p>
<p>We load all the idioms from the website, and we create <code>TextSegment</code>s, which is the class used by <a href="https://docs.langchain4j.dev/">LangChain4j</a> to pass to the embedding model for creating vectors.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Idiom<span style="color:#666">[]</span><span style="color:#bbb"> </span>idioms<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>loadIdioms();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(Idiom<span style="color:#bbb"> </span>idiom<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>idioms)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;-&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">title</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>implementation<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">implementations</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>implementation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">implementations</span><span style="color:#666">[</span>j<span style="color:#666">]</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(implementation.<span style="color:#4070a0">code</span><span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">&amp;&amp;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">               </span><span style="color:#666">!</span>implementation.<span style="color:#4070a0">code</span>.<span style="color:#4070a0">isBlank</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>allCodeSegments.<span style="color:#4070a0">add</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextSegment(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>implementation.<span style="color:#4070a0">code</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>Metadata()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;idiomId&#34;</span>,<span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">id</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;title&#34;</span>,<span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">title</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;description&#34;</span>,<span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">description</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;titleAndDescription&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>idiom.<span style="color:#4070a0">title</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">description</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;keywords&#34;</span>,<span style="color:#bbb"> </span>idiom.<span style="color:#4070a0">keywords</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;implementationId&#34;</span>,<span style="color:#bbb"> </span>implementation.<span style="color:#4070a0">id</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">put</span>(<span style="color:#4070a0">&#34;language&#34;</span>,<span style="color:#bbb"> </span>implementation.<span style="color:#4070a0">language</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice that we also add some metadata. Not only do we embed the code snippets, but we also add some extra information like the title, description, keywords, or programming language. This will be useful for showing the results found during the semantic search.</p>
<p>We create a metadata field that concatenates the title and description of the idiom, as this is useful meta-information that the embedding model can use when calculating the vector embeddings. The <code>text-embedding-005</code> model pays attention to that information, and this will influence the calculations and enrich the semantic context of the vector embedding.</p>
<h2 id="calculating-embedding-vectors">Calculating embedding vectors</h2>
<p>To compute those embeddings, we configure and use the <code>text-embedding-005</code> embedding model offered by Vertex AI. We define two instances of the model, with two distinct task types:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>VertexAiEmbeddingModel<span style="color:#bbb"> </span>EMBEDDING_MODEL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;text-embedding-005&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">taskType</span>(VertexAiEmbeddingModel.<span style="color:#4070a0">TaskType</span>.<span style="color:#4070a0">RETRIEVAL_DOCUMENT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">titleMetadataKey</span>(<span style="color:#4070a0">&#34;titleAndDescription&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxSegmentsPerBatch</span>(150)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>VertexAiEmbeddingModel<span style="color:#bbb"> </span>EMBEDDING_MODEL_FOR_RETRIEVAL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;text-embedding-005&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">taskType</span>(VertexAiEmbeddingModel.<span style="color:#4070a0">TaskType</span>.<span style="color:#4070a0">CODE_RETRIEVAL_QUERY</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">titleMetadataKey</span>(<span style="color:#4070a0">&#34;titleAndDescription&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We use the <code>EMBEDDING_MODEL</code> with a <code>RETRIEVAL_DOCUMENT</code> task type for the calculation of the vector embedding, but we use the <code>EMBEDDING_MODEL_FOR_RETRIEVAL</code> instance one, with a <code>CODE_RETRIEVAL_QUERY</code> task type for the retrieval.</p>
<p>The <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/task-types#benefits_of_task_types">documentation on task types</a> explains that it helps optimize the vector embedding calculation for different types of tasks. And this is what allows us to compare natural language queries like <code>&quot;calculating string length&quot;</code> with the actual code that computes the length of a string. Task types put the questions and answers closer in the embedding space.</p>
<p>We calculate all the embeddings in batch with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>InMemoryEmbeddingStore<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>embeddingStore<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InMemoryEmbeddingStore<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Embedding<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>allEmbeddings<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>EMBEDDING_MODEL.<span style="color:#4070a0">embedAll</span>(allCodeSegments).<span style="color:#4070a0">content</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>embeddingStore.<span style="color:#4070a0">addAll</span>(allEmbeddings,<span style="color:#bbb"> </span>allCodeSegments);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>embeddingStore.<span style="color:#4070a0">serializeToFile</span>(filePath);<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="embedding-the-query-and-searching">Embedding the query and searching</h2>
<p>With vector databases, when doing a search, we compare a vector embedding of what we’re searching for, with all the vector embeddings stored. So now that we have all our code snippets embedded, we need to compare an embedding of a user query to all those snippets. The in-memory embedding store can calculate cosine similarities between vectors for us.</p>
<p>Simplifying the code from the <a href="https://gist.github.com/glaforge/4e45fa4222dd803d6d8bbf2b9335e90d">gist</a> a little, what we do here is to calculate the embedding for the user query, and prepare an embedding search request:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Embedding<span style="color:#bbb"> </span>queryEmbedding<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>EMBEDDING_MODEL_FOR_RETRIEVAL.<span style="color:#4070a0">embed</span>(question)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">content</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>searchRequestBuilder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>EmbeddingSearchRequest.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxResults</span>(5)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">minScore</span>(0.<span style="color:#4070a0">8</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">queryEmbedding</span>(queryEmbedding)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>EmbeddingSearchResult<span style="color:#666">&lt;</span>TextSegment<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>searchResult<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>embeddingStore.<span style="color:#4070a0">search</span>(searchRequest);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We chose to return only the 5 best search results, whose minimal score is above 0.8 (the score is a value between 0 and 1, with 1 being the highest). Then, we can iterate over the hits, and display the results for this search with some formatting:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>searchResult.<span style="color:#4070a0">matches</span>().<span style="color:#4070a0">forEach</span>(match<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>TextSegment<span style="color:#bbb"> </span>matchedSegment<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>match.<span style="color:#4070a0">embedded</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            ——— %s ——— (score: %4.5f) —————————
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Title: %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Description: %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Code:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            %s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>matchedSegment.<span style="color:#4070a0">metadata</span>().<span style="color:#4070a0">getString</span>(<span style="color:#4070a0">&#34;language&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>match.<span style="color:#4070a0">score</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>matchedSegment.<span style="color:#4070a0">metadata</span>().<span style="color:#4070a0">getString</span>(<span style="color:#4070a0">&#34;title&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>matchedSegment.<span style="color:#4070a0">metadata</span>().<span style="color:#4070a0">getString</span>(<span style="color:#4070a0">&#34;description&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>matchedSegment.<span style="color:#4070a0">text</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span></code></pre></div><p>We can try different queries:</p>
<ul>
<li>How can I make an HTTP POST request?</li>
<li>How to count the characters in a string?</li>
<li>How to use the LibXML parser in Perl?</li>
</ul>
<p>For example, for the first query, the top results look interesting, with some good scores:</p>
<pre tabindex="0"><code>——— Java ——— (score: 0.85341) —————————
Title: Make HTTP POST request

Description: Make a HTTP request with method POST to the URL u

Code:
String s = HttpClient.newHttpClient().send(HttpRequest.newBuilder()
                        .uri(URI.create(u))
                        .POST(HttpRequest.BodyPublishers.ofString(content))
                        .build(), HttpResponse.BodyHandlers.ofString())
                .body();

——— D ——— (score: 0.84189) —————————
Title: Make HTTP POST request

Description: Make a HTTP request with method POST to the URL u

Code:
auto response = post(u, content);

——— Go ——— (score: 0.84010) —————————
Title: Make HTTP POST request

Description: Make a HTTP request with method POST to the URL u

Code:
response, err := http.Post(u, contentType, body)

——— Go ——— (score: 0.83938) —————————
Title: Make HTTP POST request

Description: Make a HTTP request with method POST to the URL u

Code:
response, err := http.PostForm(u, formValues)

——— Lisp ——— (score: 0.83770) —————————
Title: Make HTTP POST request

Description: Make a HTTP request with method POST to the URL u

Code:
(dex:post u)
</code></pre><p>Our search implementation found the right idioms and implementations.</p>
<h2 id="restricting-the-search-with-metadata-filtering">Restricting the search with metadata filtering</h2>
<p>Now if we try to be more specific, like our question that asks explicitly to search for a specific programming language like Perl, the search would yield results in all programming languages. But the user wanted only Perl examples! Instead, to have better and more precise results, we can take advantage of LangChain4j’s <a href="https://docs.langchain4j.dev/integrations/embedding-stores/">metadata filtering</a>.</p>
<p>You remember that we added various metadata information to our embedded text segments? We included the programming language used in the code snippet in a language metadata field. With metadata filtering, we can focus the search only on a subset of vector embeddings whose language metadata field matches the programming language we’re interested in.</p>
<p>Let’s update our search query as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>searchRequestBuilder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>EmbeddingSearchRequest.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxResults</span>(5)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">minScore</span>(0.<span style="color:#4070a0">8</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">queryEmbedding</span>(queryEmbedding)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">filter</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>IsEqualTo(<span style="color:#4070a0">&#34;language&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>programmingLanguageRecognised))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We added a <code>filter()</code> method, that checks that the language is equal to some value. But then, it means we have to know up-front that the user wants results just for one specific programming language. We could have some kind of UI element that users have to fill to select the programming language. But in our search query, we had a user providing the programming language directly in that query: <code>&quot;How to use the LibXML parser in Perl?&quot;</code></p>
<p>In such a situation, we can’t rely on a UI component or CLI parameter, we have to guess the programming language requested from the query string itself. This is where Gemini can come to the rescue, with a little bit of prompting, we can ask the generative model to tell us if a programming language is present in the query, and which one.</p>
<p>First, let’s have a look at the programming languages offered by Programming Idioms:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>KNOWN_PROGRAMMING_LANGUAGES<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;UNKNOWN&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Go&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Rust&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Python&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Perl&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Ruby&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Java&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;JS&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;C#&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Dart&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Pascal&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;PHP&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;C++&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Haskell&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;D&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Lua&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Clojure&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Fortran&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Elixir&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Kotlin&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Erlang&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;C&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Lisp&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;VB&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Groovy&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Ada&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Scala&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Scheme&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Smalltalk&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Obj-C&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Cobol&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Prolog&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Caml&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We added an <code>UNKNOWN</code> value, when the language is not specified or recognised.</p>
<p>Now we configure a Gemini 1.5 Flash model, specifying a response schema to restrict the model’s answer to a value contained in the language enumeration of possible programming languages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>ChatLanguageModel<span style="color:#bbb"> </span>GEMINI_MODEL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">addAllEnum</span>(KNOWN_PROGRAMMING_LANGUAGES)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let’s prompt Gemini to find the programming language in the user query (if present):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>programmingLanguageRecognised<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GEMINI_MODEL.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Your role is to classify the user message to decide
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            if it is a question about a particular programming
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            language or not.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            If you don&#39;t know, or if the programming language
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            is not specified, reply with `UNKNOWN`, otherwise
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            reply with just the name of the programming
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            language recognized among the following list:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>KNOWN_PROGRAMMING_LANGUAGES),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>UserMessage.<span style="color:#4070a0">from</span>(question)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>).<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini will either reply with <code>UNKNOWN</code> if no programming language was mentioned, or with the language it has recognized.</p>
<p>Now, when making a search for an idiom in a particular language, only implementations in that language are returned, giving much better results, in line with the expectations of the user.</p>
<h2 id="possible-further-improvements">Possible further improvements</h2>
<p>Where can we go from there? We can make the search a little bit snappier, or further enhance the quality of the search results.</p>
<p>Let’s talk first about the search speed. Searching through the in-memory vector database is pretty fast, and only requires a couple dozen milliseconds. After all, it’s all in memory, and there’s not millions of records in the database. But what takes more time are the round trips to the cloud hosted embedding models and for the generative model calls.</p>
<p>Depending on the cloud region you use, and from where you call the program, an embedding request can take up to a second and a half, and the Gemini call less than a second. So making a request to Gemini to guess the programming language, then calling the embedding model to embed the query for comparison with the in-memory database, would be roughly two and a half seconds long if done serially. Since both operations are unrelated, we can call them in parallel using an executor service with two threads:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>Future<span style="color:#666">&lt;</span>Object<span style="color:#666">&gt;&gt;</span><span style="color:#bbb"> </span>futures;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>executorService<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Executors.<span style="color:#4070a0">newFixedThreadPool</span>(2))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>futures<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>executorService.<span style="color:#4070a0">invokeAll</span>(List.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>recognizeProgrammingLanguage(question),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>embedQuery(question)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>programmingLanguageRecognised<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(String)<span style="color:#bbb"> </span>futures.<span style="color:#4070a0">get</span>(0).<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Embedding<span style="color:#bbb"> </span>queryEmbedding<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(Embedding)<span style="color:#bbb"> </span>futures.<span style="color:#4070a0">get</span>(1).<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>With this trick, the embedding and programming language guessing takes as much time as the longest of both tasks. Usually, it seems the embedding is the longest. So we shave a second of wait time for the user. It’s a win!</p>
<p>The other aspect we could improve further is the quality of search results. We already improved it by applying two techniques: using a code retrieval task type with our embedding model, and also the programming language filtering to avoid returning languages the user isn’t interested in.</p>
<p>However, there’s another approach we haven’t explored (this could be the topic for another article) which is to combine the existing keyword-based search provided by the Programming Idioms website, with our semantic search. This is what is called <strong>hybrid search</strong>: combining the results of two or more searches, to give better results, applying techniques like <a href="https://medium.com/@devalshah1619/mathematical-intuition-behind-reciprocal-rank-fusion-rrf-explained-in-2-mins-002df0cc5e2a">Reciprocal Rank Fusion</a> to merge results.</p>
<p>Embedding and generative models understand text pretty well, but can struggle with acronyms, product names, etc, that they haven’t seen much (if at all) in their training set. But keyword-based searches excel at that. So by combining the best of both worlds, our little website search box could tackle more queries, and give the best answers to our users.</p>
<h2 id="summary">Summary</h2>
<p>This article explored semantic code search for programming idioms using Vertex AI <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings">embedding models</a> and the <a href="https://docs.langchain4j.dev/">LangChain4j</a> framework. We aimed to enable natural language queries for code examples, going beyond keyword-based searches. Key learnings included:</p>
<ul>
<li><strong>Embedding models</strong> represented text as multidimensional vectors, capturing semantic similarities.</li>
<li><strong>Vertex AI&rsquo;s text-embedding-005</strong> model, particularly the <code>CODE_RETRIEVAL_QUERY</code> task type, was optimized for code-related searches.</li>
<li><strong>LangChain4j</strong> provided a framework for building LLM applications in Java.</li>
<li><strong>Gemini</strong>, a generative AI model, could be used to infer the programming language from a user&rsquo;s query, improving search accuracy.</li>
<li><strong>Parallel processing</strong> enhanced search speed by concurrently executing embedding and language recognition tasks.</li>
<li><strong>Metadata filtering</strong> allowed for more precise searches based on attributes like the programming language name.</li>
<li><strong>Hybrid search</strong>, combining semantic and keyword-based approaches, could further improve search quality.</li>
</ul>
<p>Overall, the article demonstrated how we could build a fast and intelligent programming idiom search engine that understands natural language queries and retrieves contextually relevant code examples.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Redacting sensitive information when using Generative AI models</title><link>https://glaforge.dev/posts/2024/11/25/redacting-sensitive-information-when-using-generative-ai-models/</link><pubDate>Mon, 25 Nov 2024 09:24:50 +0100</pubDate><guid>https://glaforge.dev/posts/2024/11/25/redacting-sensitive-information-when-using-generative-ai-models/</guid><description>&lt;p>As we are making our apps smarter with the help of Large Language Models, we must keep in mind that we are often dealing with potentially sensitive information coming from our users. In particular, in the context of chatbots, our application users have the ability to input any text in the conversation.&lt;/p>
&lt;p>Personally Identifiable Information (PII) should be dealt with the highest level of attention, because we care about our users, we don&amp;rsquo;t want to leak their personal details, and we must comply with all sorts of laws or regulations. In a word, we are &lt;a href="https://cloud.google.com/responsible-ai">responsible AI&lt;/a> developers.&lt;/p></description><content:encoded>
<![CDATA[<p>As we are making our apps smarter with the help of Large Language Models, we must keep in mind that we are often dealing with potentially sensitive information coming from our users. In particular, in the context of chatbots, our application users have the ability to input any text in the conversation.</p>
<p>Personally Identifiable Information (PII) should be dealt with the highest level of attention, because we care about our users, we don&rsquo;t want to leak their personal details, and we must comply with all sorts of laws or regulations. In a word, we are <a href="https://cloud.google.com/responsible-ai">responsible AI</a> developers.</p>
<p>In this article, we&rsquo;ll learn about the Google Cloud <a href="https://cloud.google.com/security/products/dlp">Data Loss Prevention</a> (DLP) API. It&rsquo;s a very powerful and rich service, which allows you to identify, classify, filter, redact any PII like names, passport numbers, bank account numbers, and more.</p>
<p>Today, with DLP, our goal is to redact the PII information sent by our user, before sending the user&rsquo;s message to our LLM.</p>
<p>In a nutshell (in pseudo-code), instead of doing:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>userMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;...&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(userMessage);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We want to add an instruction in the middle to redact the personally identifiable information before sending it to the LLM, so we need to to insert a method in between:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>userMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;...&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>redactedMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>redact(userMessage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(redactedMessage);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;ll simply redact the user message, but remember that there are other areas where you can apply good practices when handling user information. For example, when you store data, when you log interactions, etc.</p>
<h2 id="meet-our-user">Meet our user!</h2>
<p>Our user, let&rsquo;s call her Alicia, is a bit talkative, and shares way too much information that she should. Let&rsquo;s imagine that she is travelling, and lost her wallet, and needs some money to be wired in a rush. Maybe she could send a message to our travel application that looks as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>userMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    My name is Alicia Bob.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    My number is +33612345678, can you call me please?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Please wire some $$$ on FR7630001007941234567890185
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You can check my passport if needed, it&#39;s 78TH67845.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>;<span style="color:#bbb">
</span></span></span></code></pre></div><p>Woh! In one message she gave her name, her phone number, her bank account (IBAN), and even her passport number! But our application doesn&rsquo;t necessarily need all those details!</p>
<p>In our code, we&rsquo;re sending that information to our Gemini model, using <a href="https://docs.langchain4j.dev/">LangChain4j</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>redactedMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>redact(userMessage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(redactedMessage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(redactedMessage);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Our mission, if we accept it, is to implement the <code>redact()</code> method that will remove all the PII information from that request.</p>
<h2 id="redacting-this-redacted-message">Redacting this [REDACTED] message!</h2>
<p>First, let&rsquo;s have a look at all the code of our <code>redact()</code> method, and we&rsquo;ll explain bits and pieces further down.
You can also look at this <a href="https://gist.github.com/glaforge/c7d7188aa3ff01a0f691b1e474ec0260">gist</a> on Github with all the code as well.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">redact</span>(String<span style="color:#bbb"> </span>userMessage)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>dlp<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>DlpServiceClient.<span style="color:#4070a0">create</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>item<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ContentItem.<span style="color:#4070a0">newBuilder</span>().<span style="color:#4070a0">setValue</span>(userMessage).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>inspectConfigbuilder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>InspectConfig.<span style="color:#4070a0">newBuilder</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>redactConfig<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>DeidentifyConfig.<span style="color:#4070a0">newBuilder</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infoTypeTransfBuilder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>InfoTypeTransformations.<span style="color:#4070a0">newBuilder</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Stream.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;PERSON_NAME&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;PHONE_NUMBER&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;PASSPORT&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;IBAN_CODE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">forEach</span>(toRedact<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infoType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>InfoType.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setName</span>(toRedact)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>inspectConfigbuilder.<span style="color:#4070a0">addInfoTypes</span>(infoType);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>replaceValueConfig<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>ReplaceValueConfig.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">setNewValue</span>(Value.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">setStringValue</span>(<span style="color:#4070a0">&#34;[&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>toRedact<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;]&#34;</span>).<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>primitiveTransformation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>PrimitiveTransformation.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">setReplaceConfig</span>(replaceValueConfig).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infoTypeTransformation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>InfoTypeTransformations.<span style="color:#4070a0">InfoTypeTransformation</span>.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">addInfoTypes</span>(infoType)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">setPrimitiveTransformation</span>(primitiveTransformation)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>infoTypeTransfBuilder<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">addTransformations</span>(infoTypeTransformation);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>redactConfig.<span style="color:#4070a0">setInfoTypeTransformations</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>infoTypeTransfBuilder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>DeidentifyContentRequest<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>DeidentifyContentRequest.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setParent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>LocationName.<span style="color:#4070a0">of</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;global&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">toString</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setItem</span>(item)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setDeidentifyConfig</span>(redactConfig)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setInspectConfig</span>(inspectConfigbuilder)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>DeidentifyContentResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>dlp.<span style="color:#4070a0">deidentifyContent</span>(request);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">getItem</span>().<span style="color:#4070a0">getValue</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(IOException<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>RuntimeException(<span style="color:#4070a0">&#34;Failed to redact message.&#34;</span>,<span style="color:#bbb"> </span>e);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>As you can see, the DLP API is quite a bit verbose, but it&rsquo;s really super powerful, and is capable of more than just redacting PII information.</p>
<p>First of all, we need to create a client for the DLP service (which is <code>AutoCloseable</code>, hence the <code>try</code> with resources pattern):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>dlp<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>DlpServiceClient.<span style="color:#4070a0">create</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>...<span style="color:#bbb"> </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(...)<span style="color:#bbb"> </span>{...}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We create a <code>ContentItem</code> from our user message:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>item<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ContentItem.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setValue</span>(userMessage)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then we&rsquo;ll create some <code>InfoType</code>s which represent the different kinds of identifiable information we&rsquo;re interested in:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Stream.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;PERSON_NAME&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;PHONE_NUMBER&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;PASSPORT&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;IBAN_CODE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">forEach</span>(toRedact<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infoType<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>InfoType.<span style="color:#4070a0">newBuilder</span>().<span style="color:#4070a0">setName</span>(toRedact).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>inspectConfigbuilder.<span style="color:#4070a0">addInfoTypes</span>(infoType);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, we care only for the person&rsquo;s name, phone number, passport, and IBAN codes. But there are a ton of <a href="https://cloud.google.com/sensitive-data-protection/docs/infotypes-reference">other details we can redact</a>.</p>
<p>The next few instructions will associate a text transformation rule to transform the PII information into some redacted format. We could have used just something like <code>[REDACTED]</code> but we are going to reuse the name of the info type: <code>[PERSON_NAME]</code>, <code>[PHONE_NUMBER]</code>, <code>[PASSPORT]</code>, and <code>[IBAN_CODE]</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>replaceValueConfig<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ReplaceValueConfig.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setNewValue</span>(Value.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setStringValue</span>(<span style="color:#4070a0">&#34;[&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>toRedact<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;]&#34;</span>).<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>primitiveTransformation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>PrimitiveTransformation.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setReplaceConfig</span>(replaceValueConfig).<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>infoTypeTransformation<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>InfoTypeTransformations.<span style="color:#4070a0">InfoTypeTransformation</span>.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">addInfoTypes</span>(infoType)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setPrimitiveTransformation</span>(primitiveTransformation)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We add all those text transformations to the information type transformation builder, and then it&rsquo;s time to actually make the request to the DLP service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>DeidentifyContentRequest<span style="color:#bbb"> </span>request<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>DeidentifyContentRequest.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setParent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>LocationName.<span style="color:#4070a0">of</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;global&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span>.<span style="color:#4070a0">toString</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setItem</span>(item)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setDeidentifyConfig</span>(redactConfig)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setInspectConfig</span>(inspectConfigbuilder)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>DeidentifyContentResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>dlp.<span style="color:#4070a0">deidentifyContent</span>(request);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">getItem</span>().<span style="color:#4070a0">getValue</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We wire everything together by creating a <code>DeidentifyContentRequest</code> instance with our user message (the item) and all PII identification and transformation configuration. We configured the DLP service by passing our Google Cloud project ID, after having <a href="https://console.cloud.google.com/apis/library/dlp.googleapis.com">enabled the API</a>. We call the DLP service with <code>dlp.deidentifyContent(request)</code> and finally we can get the redacted value with <code>response.getItem().getValue()</code>.</p>
<p>So what does our original user message look like now, once redaction is applied? Let&rsquo;s see:</p>
<pre tabindex="0"><code>My name is [PERSON_NAME] [PERSON_NAME].
My number is [PHONE_NUMBER], can you call me please?
Please wire some $$$ on [IBAN_CODE]
You can check my passport if needed, it&#39;s [PASSPORT].
</code></pre><p>No more personally identifiable information left!</p>
<h2 id="summary">Summary</h2>
<p>Our user&rsquo;s trust is one of the most important things we must care about. Not only for compliance purposes but also simply because it&rsquo;s the right thing to do. There are so many hackers out there trying to get access to such information, for nefarious reasons. Let&rsquo;s not offer them an extra chance to harm our users.</p>
<p>In this article and sample code, we&rsquo;ve seen that the <a href="https://cloud.google.com/security/products/dlp?hl=en">Google Cloud DLP API</a> is able to redact information, but it can be used in a myriad of ways, for example to analyze data at rest as well, or you can deidentify / reidentify information as well. Be sure to check out what this service is capable of doing. We focused on just a few PII details, but DLP supports a <a href="https://cloud.google.com/sensitive-data-protection/docs/infotypes-reference">huge number of identifiable information</a>.</p>
<p>There&rsquo;s a big <a href="https://cloud.google.com/sensitive-data-protection/docs/samples/?hl=en">list of snippets</a> of code that you can have a look at to see what you can do with the DLP API. There are <a href="https://cloud.google.com/sensitive-data-protection/docs/libraries">SDKs</a> for various programming languages, if you use another language than Java. And check out the <a href="https://cloud.google.com/sensitive-data-protection/docs/sensitive-data-protection-overview">documentation</a>!</p>
<p>You can apply this technique to filter user input before sending it to a generative model, but you can also apply it in output as well, when/if you log user messages, or store data in databases or other places.</p>
<p>And remember, be mindful of your user&rsquo;s data!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Data extraction: The many ways to get LLMs to spit JSON content</title><link>https://glaforge.dev/posts/2024/11/18/data-extraction-the-many-ways-to-get-llms-to-spit-json-content/</link><pubDate>Mon, 18 Nov 2024 09:47:28 +0100</pubDate><guid>https://glaforge.dev/posts/2024/11/18/data-extraction-the-many-ways-to-get-llms-to-spit-json-content/</guid><description>&lt;p>Data extraction from unstructured text is a very important task where LLMs shine, as they understand human languages well. Rumor has it that 80% of the worldwide knowledge and data comes in the form of unstructured text (vs 20% for data stored in databases, spreadsheets, JSON/XML, etc.) Let’s see how we can get access to that trove of information thanks to LLMs.&lt;/p>
&lt;p>In this article, we’ll have a look at different techniques to make LLMs generate JSON output and extract data from text. This applies to most LLMs and frameworks, but for illustration purposes, we’ll use &lt;a href="https://deepmind.google/technologies/gemini/">Gemini&lt;/a> and &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a> in Java.&lt;/p></description><content:encoded>
<![CDATA[<p>Data extraction from unstructured text is a very important task where LLMs shine, as they understand human languages well. Rumor has it that 80% of the worldwide knowledge and data comes in the form of unstructured text (vs 20% for data stored in databases, spreadsheets, JSON/XML, etc.) Let’s see how we can get access to that trove of information thanks to LLMs.</p>
<p>In this article, we’ll have a look at different techniques to make LLMs generate JSON output and extract data from text. This applies to most LLMs and frameworks, but for illustration purposes, we’ll use <a href="https://deepmind.google/technologies/gemini/">Gemini</a> and <a href="https://docs.langchain4j.dev/">LangChain4j</a> in Java.</p>
<p>We’ll explore the following approaches:</p>
<ul>
<li>prompting</li>
<li>function calling</li>
<li>structured output with a JSON mode</li>
<li>structured output with a JSON response schema</li>
</ul>
<h2 id="lets-get-started">Let’s get started</h2>
<p>Your mission, if you accept it, is to extract the name and age from the biography of a person:</p>
<pre tabindex="0"><code>Anna is a 23 year old artist based in Brooklyn, New York. She
was born and raised in the suburbs of Chicago, where she developed a
love for art at a young age. She attended the School of the Art
Institute of Chicago, where she studied painting and drawing. After
graduating, she moved to New York City to pursue her art career.
Anna&#39;s work is inspired by her personal experiences and observations
of the world around her. She often uses bright colors and bold lines
to create vibrant and energetic paintings. Her work has been exhibited
in galleries and museums in New York City and Chicago.
</code></pre><p>From that text, we want to extract the following JSON snippet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;Anna&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;age&#34;</span>: <span style="color:#40a070">23</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="lets-just-ask-politely">Let’s just ask politely!</h2>
<p>The first approach is to simply craft a user message, via prompting, that requests the response to be returned as JSON. A simple prompt suffice:</p>
<pre tabindex="0"><code>Return the name and age of the person described in the biography below.
Give the name and age in the form of a JSON object following this
structure: `{&#34;name&#34;: &#34;Jon Doe&#34;, &#34;age&#34;: 36}`
Only return JSON, without any explanation,
without surrounding markdown code markup.

Here is the biography:

Anna is a 23 year old artist based in Brooklyn, New York. She
was born and raised in the suburbs of Chicago, where she developed a
love for art at a young age. She attended the School of the Art
Institute of Chicago, where she studied painting and drawing. After
graduating, she moved to New York City to pursue her art career.
Anna&#39;s work is inspired by her personal experiences and observations
of the world around her. She often uses bright colors and bold lines
to create vibrant and energetic paintings. Her work has been exhibited
in galleries and museums in New York City and Chicago.

JSON:
</code></pre><p>Sometimes, LLMs don’t always follow precisely the instructions. So you have to nudge them a little bit by requesting them to really output only JSON, as sometimes they wrap their answers with messages like “Here is the name and age of the person…” or with extra Markdown code blocks. So you may have to further tweak the prompt.</p>
<p>Quick illustration with Gemini and LangChain4j:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>biography<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Anna is a 23 year old artist…&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Return the name and age of the person described in the biography
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    below. Give the name and age in the form of a JSON object
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    following this structure: `{&#34;name&#34;: &#34;Jon Doe&#34;, &#34;age&#34;: 36}`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Only return JSON, without any explanation,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    without surrounding markdown code markup.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Here is the biography:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>biography<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    JSON:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// {&#34;name&#34;: &#34;Anna&#34;, &#34;age&#34;: 23}</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>The output is a <code>String</code>, so you have to parse it with your favorite JSON parser, but the data has been successfully extracted into a JSON object.</p>
<p>Most LLMs support the notion of system instructions. Usually, LLMs obey a bit more closely to those instructions, than via user prompts. So you could also rewrite the example above by splitting the instructions inside system instructions, and put only the biography in the user prompt.</p>
<h2 id="function-calling-to-the-rescue">Function calling to the rescue!</h2>
<p>Before the advent of JSON modes and response schemas (that we’ll review in the next sections) a more certain way to get JSON outputs was to take advantage of function calling. You have to encourage the LLM to request a function call to extract the information. Here’s the trick.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;extractNameAndAgeFromBiography&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;extract the name and age of a person described in the biographical text given in input&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;parameters&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;object&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;properties&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;string&#34;</span>
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;age&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;integer&#34;</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;required&#34;</span>: [<span style="color:#4070a0">&#34;name&#34;</span>, <span style="color:#4070a0">&#34;age&#34;</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>You define a function whose signature looks like <code>extractNameAndAgeFromBiography(String name, int age)</code>, following the OpenAPI specification. You should add very precise descriptions for the function and its arguments. Here, I could have added more information about the parameters, but the names seemed self-explanatory to me. Then you can just pass the biography directly, and it should just work out of the box.</p>
<p>You can add system instructions to request the model to call that method to find the name and age of the person. But sometimes, some LLMs also allow you to force the LLM to request a call to a function.</p>
<p>What does it look like in Java with LangChain4j?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toolCallingMode</span>(ToolCallingMode.<span style="color:#4070a0">ANY</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">allowedFunctionNames</span>(List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;extractNameAndAgeFromBiography&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>I specified the tool calling mode: this <code>ANY</code> value instructs the model to call one of the methods defined in the allowed function names list. It is a forced call request. The model will have to request the call.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          Return the name and age of the person described by the user
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          by calling the function `extractNameAndAgeFromBiography()`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">          and passing the name and the age of the person recognized.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>UserMessage.<span style="color:#4070a0">from</span>(biography)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ToolSpecification.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            extract the name and age of a person described
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            in the biographical text given in input
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;extractNameAndAgeFromBiography&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">parameters</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>JsonObjectSchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">addStringProperty</span>(<span style="color:#4070a0">&#34;name&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">addIntegerProperty</span>(<span style="color:#4070a0">&#34;age&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">required</span>(<span style="color:#4070a0">&#34;name&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;age&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>generate()</code> call is a bit more convoluted. With forced tool calling, the system message is not mandatory, but it can help ensure all parameters are passed as arguments. Look at how we defined the contract of the function <code>extractNameAndAgeFromBiography()</code> by creating an object with a string and integer properties.</p>
<p>Now we’ll extract the function call request. We don’t look at the text content, as the model returns a tool execution request instead:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">content</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toolExecutionRequests</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">getFirst</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">arguments</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// {&#34;name&#34;:&#34;Anna&#34;,&#34;age&#34;:23.0}</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>You can retrieve just the arguments, as a JSON string. It’s already following the JSON object structure we wished to obtain.</p>
<p>You might notice a minor annoyance here, though, which is the fact the age is not an integer, but a floating point number. I’m not entirely sure at this point why we don’t get an integer. I’ll have to dig a little deeper…</p>
<p>Let’s now have a look at the JSON mode and response schema approaches.</p>
<h2 id="json-mode-approach">JSON mode approach</h2>
<p>Some LLMs started offering the ability to request the model to output valid JSON. It’s not necessarily 100% certain that it will follow your requested format (for example, some JSON object keys could sometimes be named differently) but it works most of the time.</p>
<p>With the JSON mode (sometimes called structured output, or constrained decoding), we come back to our first approach, by prompting the LLM to generate JSON. But this time, we don’t have to nudge the LLM as much, because it must always generate valid JSON in output. It won’t add any Markdown markup, or any commentary.</p>
<p>Let’s see how to use the JSON mode with Gemini and LangChain4j:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice how we set the response MIME type to application/json? That’s how we enable Gemini to always return valid JSON in output.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Return the name and age of the person described in the
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            biography below. Give the name and age in the form of
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            a JSON object following this structure:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            `{&#34;name&#34;: &#34;Jon Doe&#34;, &#34;age&#34;: 36}`
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>UserMessage.<span style="color:#4070a0">from</span>(biography)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// {&#34;name&#34;: &#34;Anna&#34;, &#34;age&#34;: 23}</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>We just needed to encourage Gemini to follow the JSON structure shown in the example in the system instruction. We don’t have to give further nudges to the model to not output Markdown code markup, or to prevent it from adding extra explanations.</p>
<p>This gives great results, but to go even further and ensure that the returned JSON document is compliant with the format you really wish to get, you can also define a JSON response schema. That’s what we’re gonna see next.</p>
<h2 id="even-better-with-json-schema-for-structured-output">Even better with JSON schema for structured output</h2>
<p>In addition to the response MIME type, you can specify the JSON schema that the JSON response must comply with. Let’s complement the previous example, and add that schema definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;name&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;The name of the person described in the biography&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;age&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">INTEGER</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;The age of the person described in the biography&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">addAllRequired</span>(List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;name&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;age&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>The response should be an object with a string name, and an integer age properties.</p>
<h2 id="bonus-points-with-type-safe-objects-with-langchain4j">Bonus points with type safe objects with LangChain4j</h2>
<p>In our LangChain4j based examples, in Java, each time, the low-level APIs offered by the framework always responded with JSON strings. But as a Java developer, we’d prefer to manipulate real Java objects instead. Of course, you can take advantage of the unmarshalling capabilities of your favorite JSON library. But what if the framework provided a higher level abstraction and did all the work for you? That’s where we’ll use LangChain4j’s AI services.</p>
<p>First, let’s define a data structure to hold the name and age of our biographies, with a Java record:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">Person</span>(String<span style="color:#bbb"> </span>name,<span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>age)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The next step is to create a contract that the framework will implement for you. In input, a string biography, and in output, a Person record:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">PersonExtractor</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SystemMessage</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Your role is to extract the name and age
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        of the person described in the biography.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Person<span style="color:#bbb"> </span><span style="color:#06287e">extractPerson</span>(String<span style="color:#bbb"> </span>biography);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice how we annotate the method with a system instruction that instructs the model what its role is.</p>
<p>We still need to instantiate our chat model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(SchemaHelper.<span style="color:#4070a0">fromClass</span>(Person.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We specify again the response MIME type, and also the response schema. But we’re using a convenience method provided by the SchemaHelper class to derive a schema from a Java class (here, our Person record).</p>
<p>Now we can instantiate our person extractor contract as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>PersonExtractor<span style="color:#bbb"> </span>extractor<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiServices.<span style="color:#4070a0">create</span>(PersonExtractor.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span>model);<span style="color:#bbb">
</span></span></span></code></pre></div><p>And finally, we can pass it the biography in input:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Person<span style="color:#bbb"> </span>person<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>extractor.<span style="color:#4070a0">extractPerson</span>(bio);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(person.<span style="color:#4070a0">name</span>());<span style="color:#bbb">  </span><span style="color:#60a0b0;font-style:italic">// Anna</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(person.<span style="color:#4070a0">age</span>());<span style="color:#bbb">   </span><span style="color:#60a0b0;font-style:italic">// 23</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>We have an instance of our Person record in output that is properly populated with the name and age of the person described in our biography! That way, as Java developers, we manipulate a real Java object, in a type-safe manner! Our application is enhanced by an LLM, but from a developer perspective, we manipulate interfaces and objects.</p>
<h2 id="summary">Summary</h2>
<p>Lots of articles, videos, or presentations often talk about the chatbot use case, when creating applications powered by large language models. However, data extraction is another very important and useful task where LLMs shine.</p>
<p>In this article, we saw different approaches to do data extraction: via prompting, function calling, or with a JSON mode or JSON schema. If your LLM supports the ability to set a response schema, that’s definitely the best way to get the JSON output you expect.</p>
<p>Also, if the LLM orchestration framework you use supports it, be sure to check if it’s able to return type-safe objects that you can manipulate with your programming language directly, without having to parse the JSON string yourself.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Things you never dared to ask about LLMs</title><link>https://glaforge.dev/talks/2024/10/24/things-you-never-dared-to-ask-about-llms/</link><pubDate>Thu, 24 Oct 2024 16:53:04 +0200</pubDate><guid>https://glaforge.dev/talks/2024/10/24/things-you-never-dared-to-ask-about-llms/</guid><description>&lt;p>Along my learning journey about generative AI, lots of questions popped up in my mind.
I was very curious to learn how things worked under the hood in Large Language Models (at least having an intuition rather than knowing the maths in and out).
Sometimes, I would wonder about how tokens are created, or how hyperparameters influence text generation.&lt;/p>
&lt;p>Before the &lt;a href="https://www.dotai.io/">dotAI&lt;/a> conference, I was invited to talk at the meetup organised by &lt;a href="https://www.datastax.com/">DataStax&lt;/a>.
I presented about all those things you never dared to ask about LLMs, sharing both the questions I came up with while learning about generative AI, and the answers I found and discovered along the way.&lt;/p></description><content:encoded>
<![CDATA[<p>Along my learning journey about generative AI, lots of questions popped up in my mind.
I was very curious to learn how things worked under the hood in Large Language Models (at least having an intuition rather than knowing the maths in and out).
Sometimes, I would wonder about how tokens are created, or how hyperparameters influence text generation.</p>
<p>Before the <a href="https://www.dotai.io/">dotAI</a> conference, I was invited to talk at the meetup organised by <a href="https://www.datastax.com/">DataStax</a>.
I presented about all those things you never dared to ask about LLMs, sharing both the questions I came up with while learning about generative AI, and the answers I found and discovered along the way.</p>
<p>Without further ado, here&rsquo;s the deck:</p>
<script async class="speakerdeck-embed" data-id="476be803290048d6935e585bf87d1e5f" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="abstract">Abstract</h2>
<blockquote>
<h2 id="things-you-never-dared-to-ask-about-llms">Things you never dared to ask about LLMs</h2>
<p>Large Language Models (LLMs) have taken the world by storm, powering applications from chatbots to content generation.
Yet, beneath the surface, these models remain enigmatic.</p>
<p>This presentation will “delve” into the hidden corners of LLM technology that often leave developers scratching their heads.
It’s time to ask those questions you’ve never dared ask about the mysteries underpinning LLMs.</p>
<p>Here are some questions we’ll to answer:</p>
<p>Do you wonder why LLMs spit tokens instead of words? Where do those tokens come from?</p>
<ul>
<li>What’s the difference between a “foundation” / “pre-trained” model, and an “instruction-tuned” one?</li>
<li>We’re often tweaking (hyper)parameters like temperature, top-p, top-k, but do you know how they really affect how tokens are picked up?</li>
<li>Quantization makes models smaller, but what are all those number encodings like fp32, bfloat16, int8, etc?</li>
<li>LLMs are good at translation, right? Do you speak the Base64 language too?</li>
</ul>
<p>We’ll realize together that LLMs are far from perfect:</p>
<ul>
<li>We’ve all heard about hallucinations, or should we say confabulations?</li>
<li>What is this reversal curse that makes LLMs ignore some facts from a different viewpoint?</li>
<li>You’d think that LLMs are deterministic at low temperature, but you’d be surprised by how the context influences LLMs’ answers…</li>
</ul>
<p>Buckle up, it’s time to dispel the magic of LLMs, and ask those questions we never dared to ask!</p></blockquote>
<p>This talk wasn&rsquo;t recorded, but I hope to give this presentation again sometime soon, and hopefully, it&rsquo;ll be recorded then.
If that happens, I&rsquo;ll share the video recording once it&rsquo;s available.</p>
<h2 id="illustrations-imagen-3-to-the-rescure">Illustrations: Imagen 3 to the rescure</h2>
<p>For those who are curious about the cute little robots that appear in this presentation,
I&rsquo;ve generated them with DeepMind&rsquo;s <a href="https://deepmind.google/technologies/imagen-3/">Imagen 3</a> image generation model.</p>
<p>The quality of the output was really lovely, and I might have been a bit overboard with the number of generated robots in this deck.</p>
<p>I would start pretty much all my prompts with <em>&ldquo;cartoon of a cute little robot&hellip;&rdquo;</em></p>
<p>For my Java developer friends, you can <a href="https://glaforge.dev/posts/2024/10/01/ai-nktober-generating-ink-drawings-with-imagen/">generate images with Imagen via LangChain4j</a>
(as explained in that article where I generated black&rsquo;n white ink drawings).</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Advanced RAG Techniques</title><link>https://glaforge.dev/talks/2024/10/14/advanced-rag-techniques/</link><pubDate>Mon, 14 Oct 2024 10:11:14 +0200</pubDate><guid>https://glaforge.dev/talks/2024/10/14/advanced-rag-techniques/</guid><description>&lt;p>&lt;strong>Retrieval Augmented Generation&lt;/strong> (RAG) is a pattern to let you prompt a large language model (LLM) about your own data, via in-context learning by providing extracts of documents found in a vector database (or potentially other sources too).&lt;/p>
&lt;p>Implementing RAG isn&amp;rsquo;t very complicated, but the results you get are not necessarily up to your expectations. In the presentations below, I explore various &lt;strong>advanced techniques to improve the quality of the responses returned by your RAG system&lt;/strong>:&lt;/p></description><content:encoded>
<![CDATA[<p><strong>Retrieval Augmented Generation</strong> (RAG) is a pattern to let you prompt a large language model (LLM) about your own data, via in-context learning by providing extracts of documents found in a vector database (or potentially other sources too).</p>
<p>Implementing RAG isn&rsquo;t very complicated, but the results you get are not necessarily up to your expectations. In the presentations below, I explore various <strong>advanced techniques to improve the quality of the responses returned by your RAG system</strong>:</p>
<p>Ingestion chunking techniques like:</p>
<ul>
<li>Embedding of sliding windows of sentences</li>
<li>Hypothetical question embedding</li>
<li>Contextual retrieval embedding (invented recently by Anthropic)</li>
<li>Semantic chunking (created by Greg Kamradt)</li>
</ul>
<p>Retrieval techniques, including:</p>
<ul>
<li>Query compression</li>
<li>Hypothetical Document Embedding (HyDE)</li>
</ul>
<p>And I also mention how an <em>agentic</em> approach can help for more advanced and complex needs, with providing intermerdiary results, combined in a final response. <strong>Agentic RAG</strong> is a very important and promising approach that I&rsquo;ll certainly come back to in upcoming articles.</p>
<p>At Devoxx Belgium 2024, I gave a 50-minute session, and a 3-hour long deep dive with my friend <a href="https://x.com/clunven">Cédrick Lunven</a> from Datastax (we used the great <a href="https://www.datastax.com/products/datastax-astra">Astra DB</a> vector database in our demos). You&rsquo;ll find both decks and videos below.</p>
<h2 id="code-available-on-github">Code available on Github</h2>
<p>All the code presented in those sessions is available in this <a href="https://github.com/datastaxdevs/conference-2024-devoxx/">Github repository</a></p>
<h2 id="rag-from-dumb-implementation-to-serious-results">RAG: from dumb implementation to serious results</h2>
<h3 id="abstract">Abstract</h3>
<blockquote>
<p>Embarking on your RAG journey may seem effortless, but achieving satisfying results often proves challenging. Inaccurate, incomplete, or outdated answers, suboptimal document retrieval, and poor text chunking can quickly dampen your initial enthusiasm.</p>
<p>In this session, we&rsquo;ll leverage LangChain4j to elevate your RAG implementations. We&rsquo;ll explore:</p>
<ul>
<li>Advanced Chunking Strategies: Optimize document segmentation for improved context and relevance.</li>
<li>Query Refinement Techniques: Expand and compress queries to enhance retrieval accuracy.</li>
<li>Metadata Filtering: Leverage metadata to pinpoint the most relevant documents.</li>
<li>Document Reranking: Reorder retrieved documents for optimal result presentation.</li>
<li>Data Lifecycle Management: Implement processes to maintain data freshness and relevance.</li>
<li>Evaluation and Presentation: Assess the effectiveness of your RAG pipeline and deliver results that meet user expectations.</li>
</ul>
<p>Join us as we transform your simplistic RAG experience from one of frustration to delight your users with meaningful and accurate answers.</p></blockquote>
<h3 id="presentation-slide-deck">Presentation slide deck</h3>
<script async class="speakerdeck-embed" data-id="a2207c4bc9b9447da5a397107da19d0f" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h3 id="youtube-video-recording">YouTube video recording</h3>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/6_wUUYKBdE0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="rag-from-dumb-implementation-to-serious-results-1">RAG: from dumb implementation to serious results</h2>
<h3 id="abstract-1">Abstract</h3>
<blockquote>
<p>It’s easy to get started with Retrieval Augmented Generation, but you’ll quickly be disappointed with the generated answers: inaccurate or incomplete, missing context or outdated information, bad text chunking strategy, not the best documents returned by your vector database, and the list goes on.</p>
<p>After meeting thousands of developers across Europe, we’ve explored those pain points, and will share with you how to overcome them. As part of the team building a vector database we are aware of the different flavors of searches (semantic, meta-data, full text, multimodal) and embedding model choices. We have been implementing RAG pipelines across different projects and frameworks and are contributing to LangChain4j.</p>
<p>In this deep-dive, we will examine various techniques using LangChain4j to bring your RAG to the next level: with semantic chunking, query expansion &amp; compression, metadata filtering, document reranking, data lifecycle processes, and how to best evaluate and present the results to your users.</p></blockquote>
<h3 id="presentation-slide-deck-1">Presentation slide deck</h3>
<script async class="speakerdeck-embed" data-id="5f7120a2dbeb4ffd917102321231cbc0" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h3 id="youtube-video-recording-1">YouTube video recording</h3>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/RN7thifOmkI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Gemini and Gemma tokenizer in Java</title><link>https://glaforge.dev/posts/2024/10/04/a-gemini-and-gemma-tokenizer-in-java/</link><pubDate>Fri, 04 Oct 2024 15:41:10 +0200</pubDate><guid>https://glaforge.dev/posts/2024/10/04/a-gemini-and-gemma-tokenizer-in-java/</guid><description>&lt;p>It&amp;rsquo;s always interesting to know &lt;em>how the sausage is made&lt;/em>, don&amp;rsquo;t you think?
That&amp;rsquo;s why, a while ago, I looked at
&lt;a href="https://glaforge.dev/posts/2024/02/05/visualize-palm-based-llm-tokens/">embedding model tokenization&lt;/a>,
and I implemented a little &lt;a href="https://tokens-lpj6s2duga-ew.a.run.app/">visualization&lt;/a> to see the tokens in a colorful manner.
Yet, I was still curious to see how Gemini would tokenize text&amp;hellip;&lt;/p>
&lt;p>Both LangChain4j Gemini modules (from
&lt;a href="https://docs.langchain4j.dev/integrations/language-models/google-vertex-ai-gemini">Vertex AI&lt;/a> and from
&lt;a href="https://docs.langchain4j.dev/integrations/language-models/google-ai-gemini">Google AI Labs&lt;/a>)
can count the tokens included in a piece of text.
However, both do so by calling a REST API endpoint method called &lt;code>countTokens&lt;/code>.
This is not ideal, as it requires a network hop to get the token counts, thus adding undesired extra latency.
Wouldn&amp;rsquo;t it be nicer if we could count tokens locally instead?&lt;/p></description><content:encoded>
<![CDATA[<p>It&rsquo;s always interesting to know <em>how the sausage is made</em>, don&rsquo;t you think?
That&rsquo;s why, a while ago, I looked at
<a href="https://glaforge.dev/posts/2024/02/05/visualize-palm-based-llm-tokens/">embedding model tokenization</a>,
and I implemented a little <a href="https://tokens-lpj6s2duga-ew.a.run.app/">visualization</a> to see the tokens in a colorful manner.
Yet, I was still curious to see how Gemini would tokenize text&hellip;</p>
<p>Both LangChain4j Gemini modules (from
<a href="https://docs.langchain4j.dev/integrations/language-models/google-vertex-ai-gemini">Vertex AI</a> and from
<a href="https://docs.langchain4j.dev/integrations/language-models/google-ai-gemini">Google AI Labs</a>)
can count the tokens included in a piece of text.
However, both do so by calling a REST API endpoint method called <code>countTokens</code>.
This is not ideal, as it requires a network hop to get the token counts, thus adding undesired extra latency.
Wouldn&rsquo;t it be nicer if we could count tokens locally instead?</p>
<p>Interestingly, both Gemini and the open-weights <a href="https://ai.google.dev/gemma">Gemma</a>
models share the same tokenizer and token vocabulary.
Also, the tokenizer is based on <a href="https://github.com/google/sentencepiece">SentencePiece</a>,
which is a tokenizer/detokenizer implementing the byte-pair-encoding (BPE) and unigram language algorithms.</p>
<p>If you look at the <a href="https://huggingface.co/google/gemma-2-9b-it/tree/main">Gemma code on HuggingFace</a>,
you&rsquo;ll see a <code>tokenizer.json</code> file that you can open to see the available tokens in the vocabulary,
and a <code>tokenizer.model</code> file which is some kind of binary compressed variation.</p>
<p>Knowing that the list of tokens supported by Gemini and Gemma is available in those files, and how they are encoded,
I was curious to see if I could implement a Java tokenizer that could run locally, rather than calling a remote endpoint.</p>
<p>The <code>SentencePiece</code> implementation from Google is a C++ library, but I didn&rsquo;t really feel like wrapping it myself with JNI,
and fortunately, I discovered that the <a href="https://djl.ai/">DJL</a> project had done the JNI wrapping job already.</p>
<p>So let&rsquo;s see how to tokenize text for Gemini and Gemma, in Java!</p>
<h2 id="gemini-and-gemma-tokenization-in-java-with-djl">Gemini and Gemma tokenization in Java with DJL</h2>
<p>First of all, let&rsquo;s setup the dependency on DJL&rsquo;s <code>SentencePiece</code> module:</p>
<ul>
<li>From Maven:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>ai.djl.sentencepiece<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>sentencepiece<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.30.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><ul>
<li>From Gradle:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>implementation <span style="color:#4070a0">&#39;ai.djl.sentencepiece:sentencepiece:0.30.0&#39;</span>
</span></span></code></pre></div><p>I saved the <code>tokenizer.model</code> file locally.
Note that it&rsquo;s a 4MB file, as Gemini/Gemma have a very large vocabulary of around a quarter million of tokens!</p>
<p>Now, let&rsquo;s instantiate an <code>SpTokenizer</code> object that loads this vocabulary file, and tokenize some text:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">ai.djl.sentencepiece.SpTokenizer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// ...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Path<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;src/test/resources/gemini/tokenizer.model&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>modelFileBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Files.<span style="color:#4070a0">readAllBytes</span>(model);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(SpTokenizer<span style="color:#bbb"> </span>tokenizer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SpTokenizer(modelFileBytes))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>tokens<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>tokenizer.<span style="color:#4070a0">tokenize</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    When integrating an LLM into your application to extend it and \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    make it smarter, it&#39;s important to be aware of the pitfalls and \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    best practices you need to follow to avoid some common problems \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    and integrate them successfully. This article will guide you \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    through some key best practices that I&#39;ve come across.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(String<span style="color:#bbb"> </span>token:<span style="color:#bbb"> </span>tokens)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;[%s]%n&#34;</span>,<span style="color:#bbb"> </span>token);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Token count: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>tokens.<span style="color:#4070a0">size</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>When running this Java class, you&rsquo;ll see the following output:</p>
<pre tabindex="0"><code>[When]
[▁integrating]
[▁an]
[▁L]
[LM]
[▁into]
[▁your]
[▁application]
...

Token count: 61
</code></pre><h2 id="next-steps">Next steps</h2>
<p>Do we need next steps? Yes, why not!
My idea is to contribute a tokenizer module to LangChain4j,
so that the Vertex AI Gemini and the Google AI Gemini modules can both import it,
instead of relying on remote endpoint calls to count tokens.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>AI Inktober — Generating ink drawings with Imagen 3</title><link>https://glaforge.dev/posts/2024/10/01/ai-nktober-generating-ink-drawings-with-imagen/</link><pubDate>Mon, 30 Sep 2024 21:25:46 +0200</pubDate><guid>https://glaforge.dev/posts/2024/10/01/ai-nktober-generating-ink-drawings-with-imagen/</guid><description>&lt;p>Every year, in October, takes place the &lt;a href="https://inktober.com/">Inktober challenge&lt;/a>:
every day of the month, you have to do a drawing representing the word of the day.
The list of &lt;em>prompts&lt;/em> this year is the following:&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-8fe9d17b97ed78189b0ebc708c476b25">
&lt;img src="https://glaforge.dev/img/ainktober/prompts.png"
alt="Inktober 2024 prompts"
/>
&lt;/a>
&lt;figcaption>Inktober 2024 prompts&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-8fe9d17b97ed78189b0ebc708c476b25">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/ainktober/prompts.png"
alt="Inktober 2024 prompts"
/>
&lt;div class="lightbox-caption">Inktober 2024 prompts&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>I participated to some of the daily challenges the past few years, but I never did all of them.
But this year, for the fun, I thought I could ask Google&amp;rsquo;s
&lt;a href="https://deepmind.google/technologies/imagen-3/">Imagen 3&lt;/a> image model to draw for me!
(Or at least to draw something I could try to reproduce.)&lt;/p></description><content:encoded>
<![CDATA[<p>Every year, in October, takes place the <a href="https://inktober.com/">Inktober challenge</a>:
every day of the month, you have to do a drawing representing the word of the day.
The list of <em>prompts</em> this year is the following:</p>
<p><figure>
  <a href="#img-8fe9d17b97ed78189b0ebc708c476b25">
    <img src="/img/ainktober/prompts.png"
      alt="Inktober 2024 prompts"
       />
  </a>
  <figcaption>Inktober 2024 prompts</figcaption>
</figure>
<div class="lightbox" id="img-8fe9d17b97ed78189b0ebc708c476b25">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/ainktober/prompts.png"
    alt="Inktober 2024 prompts"
     />
  <div class="lightbox-caption">Inktober 2024 prompts</div>
</div>
</p>
<p>I participated to some of the daily challenges the past few years, but I never did all of them.
But this year, for the fun, I thought I could ask Google&rsquo;s
<a href="https://deepmind.google/technologies/imagen-3/">Imagen 3</a> image model to draw for me!
(Or at least to draw something I could try to reproduce.)</p>
<p>Of course, the goal of the challenge is not to generate images with the help of an AI.
On the contrary, the idea is about the pleasure you can have drawing yourself, with your own hands!
However, I was curious to see how Imagen would perform on such a challenge.</p>
<p>So I fired up my favorite Java AI framework: <a href="https://docs.langchain4j.dev/">LangChain4j</a>,
as it supports Imagen 3, as image model.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Imagen 3 is generally available on Google Cloud&rsquo;s Vertex AI platform, but it&rsquo;s behind an <em>allow list</em>.
So you have to <a href="https://docs.google.com/forms/d/1cqt9padvfMgqn23W5FMPTqh7bW1KLkEOsC5G6uC-uuM/viewform">request access</a> to be able to use it.</p>
      </div>
    </div><p>You will need the following dependency:</p>
<ul>
<li>For Gradle users:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>implementation <span style="color:#4070a0">&#39;dev.langchain4j:langchain4j-vertex-ai:0.35.0&#39;</span>
</span></span></code></pre></div><ul>
<li>For Maven users:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>dev.langchain4j<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>langchain4j-vertex-ai<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.35.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>Now let&rsquo;s have a look at the code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.data.image.Image</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.output.Response</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.VertexAiImageModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.nio.file.Path</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">AInktober</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>VertexAiImageModel<span style="color:#bbb"> </span>imagenModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">endpoint</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_VERTEXAI_ENDPOINT&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagen-3.0-fast-generate-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">aspectRatio</span>(VertexAiImageModel.<span style="color:#4070a0">AspectRatio</span>.<span style="color:#4070a0">SQUARE</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">negativePrompt</span>(<span style="color:#4070a0">&#34;watercolor, gray shades&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">persistTo</span>(Path.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;/tmp/imagen&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>prompt<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            A black and white ink drawing of a
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            backpack, on a fully white background
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imageResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>imagenModel.<span style="color:#4070a0">generate</span>(prompt);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(imageResponse.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">url</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>I have set up several environment variables containing my Google Cloud project details.</li>
<li>I decided to use <code>imagen-3.0-fast-generate-001</code>, which generates images faster (and cheaper!) than <code>imagen-3.0-generate-001</code>
at the cost of a slightly lower quality (but for ink drawings, that&rsquo;s not really visible).</li>
<li>I went with square images, but you can use landscape, portrait, and wider variants too.</li>
<li>I added a negative prompt, because some images looked a bit more like watercolor at times, but I wanted images more black and white.</li>
<li>I persist all the generated images into a temporary folder.</li>
<li>My prompt contains the first subject of the day, a <em>&ldquo;backpack&rdquo;</em>, and I specify that I want a black and white ink drawing,
but I also added that I wanted a white background, as sometimes the background can be fully black, or some sepia shade.</li>
</ul>
<p>So what does the first image look like?</p>
<p><figure>
  <a href="#img-a3f0dadbd4b24ec339369e1cd9e0d2d3">
    <img src="/img/ainktober/ainktober-01-backpack.png"
      alt="Inktober 2024&rsquo;s backpack"
       />
  </a>
  <figcaption>Inktober 2024&rsquo;s backpack</figcaption>
</figure>
<div class="lightbox" id="img-a3f0dadbd4b24ec339369e1cd9e0d2d3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/ainktober/ainktober-01-backpack.png"
    alt="Inktober 2024&rsquo;s backpack"
     />
  <div class="lightbox-caption">Inktober 2024&rsquo;s backpack</div>
</div>
</p>
<p>It definitely looks like an ink drawing of a backpack!</p>
<p>Don&rsquo;t worry, I won&rsquo;t post a new article each day for the new daily image prompt.
Instead, I&rsquo;ll share the other days on my usual social media channels (see the bottom of the blog to find them out.)</p>
<p>Be sure to checkout Imagen 3, it&rsquo;s pretty good!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Lots of new cool Gemini stuff in LangChain4j 0.35.0</title><link>https://glaforge.dev/posts/2024/09/29/lots-of-new-cool-gemini-stuff-in-langchain4j/</link><pubDate>Wed, 25 Sep 2024 13:41:19 +0200</pubDate><guid>https://glaforge.dev/posts/2024/09/29/lots-of-new-cool-gemini-stuff-in-langchain4j/</guid><description>&lt;p>While &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a> 0.34 introduced my
&lt;a href="http://localhost:1313/posts/2024/09/05/new-gemini-model-in-langchain4j/">new Google AI Gemini module&lt;/a>,
a new 0.35.0 version is already here today, with some more cool stuff for Gemini and Google Cloud!&lt;/p>
&lt;p>Let&amp;rsquo;s have a look at what&amp;rsquo;s in store!&lt;/p>
&lt;h2 id="gemini-15-pro-002-and-gemini-15-flash-002">Gemini 1.5 Pro 002 and Gemini 1.5 Flash 002&lt;/h2>
&lt;p>This week, &lt;a href="https://developers.googleblog.com/en/updated-production-ready-gemini-models-reduced-15-pro-pricing-increased-rate-limits-and-more/">Google announced&lt;/a>
the release of the new versions of the Google 1.5 models:&lt;/p>
&lt;ul>
&lt;li>&lt;code>google-1.5-pro-002&lt;/code>&lt;/li>
&lt;li>&lt;code>google-1.5-flash-002&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Of course, both models are supported by LangChain4j!
The Google AI Gemini module also supports the &lt;code>gemini-1.5-flash-8b-exp-0924&lt;/code> 8-billion parameter model.&lt;/p></description><content:encoded>
<![CDATA[<p>While <a href="https://docs.langchain4j.dev/">LangChain4j</a> 0.34 introduced my
<a href="http://localhost:1313/posts/2024/09/05/new-gemini-model-in-langchain4j/">new Google AI Gemini module</a>,
a new 0.35.0 version is already here today, with some more cool stuff for Gemini and Google Cloud!</p>
<p>Let&rsquo;s have a look at what&rsquo;s in store!</p>
<h2 id="gemini-15-pro-002-and-gemini-15-flash-002">Gemini 1.5 Pro 002 and Gemini 1.5 Flash 002</h2>
<p>This week, <a href="https://developers.googleblog.com/en/updated-production-ready-gemini-models-reduced-15-pro-pricing-increased-rate-limits-and-more/">Google announced</a>
the release of the new versions of the Google 1.5 models:</p>
<ul>
<li><code>google-1.5-pro-002</code></li>
<li><code>google-1.5-flash-002</code></li>
</ul>
<p>Of course, both models are supported by LangChain4j!
The Google AI Gemini module also supports the <code>gemini-1.5-flash-8b-exp-0924</code> 8-billion parameter model.</p>
<p>Versions <code>002</code> come with:</p>
<ul>
<li>much improved math and reasoning capabilities <br />
(7%-20% increase depending on the benchmark),</li>
<li>2x faster output, and 3x lower latency,</li>
<li>and also roughly a 50% price cut!</li>
</ul>
<h2 id="google-cloud-storage-document-loader">Google Cloud Storage document loader</h2>
<p>When implementing Retrieval Augmented Generation (RAG), you must load the documents from somewhere.
You can feed the docs directly in the context, but LangChain4j comes with the notion of <a href="https://docs.langchain4j.dev/tutorials/rag#document-loader">document loaders</a>.
There are existing document loaders for the file system, for files at remote URLs, or source files stored in Github.</p>
<p>In this release, I&rsquo;ve implemented a <strong>Google Cloud Storage document loader</strong>,
which lets you reference documents stored inside cloud storage buckets.</p>
<p>Create a GCS document loader with the new builder:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>gcsLoader<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleCloudStorageDocumentLoader.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then you can load a single document, and parse it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Document<span style="color:#bbb"> </span>document<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gcsLoader.<span style="color:#4070a0">loadDocument</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;BUCKET_NAME&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;FILE_NAME.txt&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextDocumentParser());<span style="color:#bbb">
</span></span></span></code></pre></div><p>All the documents in a bucket:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>Document<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>documents<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gcsLoader.<span style="color:#4070a0">loadDocuments</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;BUCKET_NAME&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextDocumentParser());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Or just the a list of files filtered with a <em>glob</em> pattern:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>Document<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>documents<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gcsLoader.<span style="color:#4070a0">loadDocuments</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;BUCKET_NAME&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;*.txt&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>TextDocumentParser());<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="vertex-ai-ranking-api">Vertex AI Ranking API</h2>
<p>When implementing Retrieval Augmented Generation (RAG), your vector database returns a certain number of results.
They are usually sorted by vector similarity.
But it&rsquo;s not necessarily because the vectors have the highest similarity, that they are necessarily the best matches to answer a user query.
In order to palliate this problem, there are ranking or reranking APIs and models that exist to order results according to how well they match the query.</p>
<p>The Vertex AI platform from Google Cloud offers a <a href="https://cloud.google.com/generative-ai-app-builder/docs/ranking">ranking API</a>
for that purpose, a little known API that deserves more awareness.
I implemented a <code>ScoringModel</code> for this Vertex AI Ranking API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiScoringModel<span style="color:#bbb"> </span>scoringModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiScoringModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">projectId</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">projectNumber</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_NUMBER&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">projectLocation</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;semantic-ranker-512&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>List<span style="color:#666">&lt;</span>Double<span style="color:#666">&gt;&gt;</span><span style="color:#bbb"> </span>score<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>scoringModel.<span style="color:#4070a0">scoreAll</span>(Stream.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;The sky appears blue due to a phenomenon called Rayleigh &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;scattering. Sunlight is comprised of all the colors of &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;the rainbow. Blue light has shorter wavelengths than other &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;colors, and is thus scattered more easily.&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;A canvas stretched across the day,\n&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Where sunlight learns to dance and play.\n&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Blue, a hue of scattered light,\n&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;A gentle whisper, soft and bright.&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>).<span style="color:#4070a0">map</span>(TextSegment::from).<span style="color:#4070a0">collect</span>(Collectors.<span style="color:#4070a0">toList</span>()),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// [0.8199999928474426, 0.4300000071525574]</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>In the example above, a user asks <em>why the sky is blue</em>.
The Ranking API attempts to determine which of two excerpts best matches this question.
The first excerpt appears to be an explanation of this celestial phenomenon, while the second sounds more like a poem.
When scoring these text fragments, we observe that the first one has a higher value (0.82 vs. 0.43).</p>
<p>It is also possible to score just one piece of text with the <code>score(text, query)</code> and <code>score(segment, query)</code> methods.</p>
<p>Now what&rsquo;s interesting is that this LangChain4j notion of scoring models is also well integrated in the RAG pipeline:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiScoringModel<span style="color:#bbb"> </span>scoringModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiScoringModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">projectId</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">projectNumber</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_NUM&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">projectLocation</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">model</span>(<span style="color:#4070a0">&#34;semantic-ranker-512&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ContentAggregator<span style="color:#bbb"> </span>contentAggregator<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ReRankingContentAggregator.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">scoringModel</span>(scoringModel)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>RetrievalAugmentor<span style="color:#bbb"> </span>retrievalAugmentor<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>DefaultRetrievalAugmentor.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">contentAggregator</span>(contentAggregator)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(Assistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatLanguageModel</span>(...)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">retrievalAugmentor</span>(retrievalAugmentor)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>When creating the AI service, you specify the chat model to use.
Additionally, you can integrate a <em>retrieval augmentor</em>, which allows you to configure a <em>content aggregator</em>.
The content aggregator, in turn, can specify a <em>scoring model</em>.
This process involves three steps, but it enables you to leverage the ranking of semantic search results when implementing RAG.
This means you can prioritize the most relevant results based on their semantic similarity, not solely on their vector similarity.</p>
<h2 id="new-parameters-for-the-vertex-ai-embedding-models">New parameters for the Vertex AI embedding models</h2>
<p>Embedding models are critical for RAG, and LangChain4j has had support for the Google Cloud Vertex AI embedding models for a long time.
But there are a couple of new flags that have recently been introduced:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>EmbeddingModel<span style="color:#bbb"> </span>embeddingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(MODEL_NAME)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">autoTruncate</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputDimensionality</span>(512)<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>autoTruncate(true)</code> method automatically truncates text to embed to a maximum of 2048 tokens.
If your input is longer than this limit, you would get an error from the model.
With auto-truncation, no more error, but if your text is truncated, you might miss a bit of meaning from the part that was cut off.</p>
<p>The other new method is <code>outputDimensionality(512)</code>.
The Vertex AI embedding models usually default to 768-dimensional vectors.
However, our <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-text-embeddings">latest embedding models</a>
are <a href="https://huggingface.co/blog/matryoshka"><em>Matryoshka</em> embedding models</a>,
which means that the most meaningful values in the vector comes first.
So when you do vector comparisons, you can make calculations quicker if you focus on the lowest dimensions,
and with this new method, you can just return vectors with less dimensions directly.</p>
<h2 id="google-ai-embedding-model">Google AI embedding model</h2>
<p>Speaking of embedding models, if you use the Google AI Gemini model instead of the Vertex AI flavor,
you can now also access our embedding models without relying on the Vertex AI models,
thanks to the new embedding model for Google AI:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>embeddingModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(GOOGLE_AI_GEMINI_API_KEY)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;embedding-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxRetries</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logRequestsAndResponses</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">titleMetadataKey</span>(<span style="color:#4070a0">&#34;title&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">taskType</span>(GoogleAiEmbeddingModel.<span style="color:#4070a0">TaskType</span>.<span style="color:#4070a0">RETRIEVAL_DOCUMENT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">outputDimensionality</span>(512)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>This new embedding model is the same as the one coming from Vertex AI, and has the same feature set.</p>
<h2 id="google-ai-gemini-token-count-estimation-and-tokenizer">Google AI Gemini token count estimation and tokenizer</h2>
<p>The Google AI Gemini model implements the <code>TokenCountEstimator</code> interface,
which means you can use the <code>estimateTokenCount()</code> method to count tokens:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(GOOGLE_AI_GEMINI_API_KEY)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">int</span><span style="color:#bbb"> </span>countedTokens<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">estimateTokenCount</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;What is the capital of France?&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>There is also now a <code>GoogleAiGeminiTokenizer</code> class, implementing the misnamed <code>Tokenizer</code> interface
(misnamed because it&rsquo;s not tokenizing text, it&rsquo;s just counting tokens):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>geminiTokenizer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiTokenizer.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(GOOGLE_AI_GEMINI_API_KEY)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#902000">int</span><span style="color:#bbb"> </span>count<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>tokenizer.<span style="color:#4070a0">estimateTokenCountInText</span>(<span style="color:#4070a0">&#34;Hello world!&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Note that both the <code>estimateTokenCount()</code> method and the <code>GoogleAiGeminiTokenizer</code> call a remote API endpoint.
They don&rsquo;t use a tokenizer class to count the tokens, so those calls incur some network hops.</p>
<p>What&rsquo;s interesting with the <code>Tokenizer</code>s is that they can be used by document splitters to split documents according to the number of tokens, rather than by characters or other boundaries:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>DocumentSplitter<span style="color:#bbb"> </span>splitter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>DocumentSplitters.<span style="color:#4070a0">recursive</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>maxSegmentSizeInTokens,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>maxOverlapSizeInTokens,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>geminiTokenizer);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Currently, only the Google AI module implements this <code>Tokenizer</code> interface, but it can be used with the Vertex AI Gemini module as well.
But later down the road, I think I&rsquo;ll also implement it for the Vertex AI module.</p>
<h2 id="chat-listener-support">Chat listener support</h2>
<p>Both the Google AI Gemini and the Vertex AI modules implement the new chat listener support.</p>
<ul>
<li>Vertex AI Gemini:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">listeners</span>(singletonList(listener))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>Google AI Gemini:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">listeners</span>(singletonList(listener))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s have a look at the listener interface, which allows you to listen to model requests, responses, and errors:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">ChatModelListener</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">default</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">onRequest</span>(ChatModelRequestContext<span style="color:#bbb"> </span>reqContext)<span style="color:#bbb"> </span>{...}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">default</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">onResponse</span>(ChatModelResponseContext<span style="color:#bbb"> </span>respContext)<span style="color:#bbb"> </span>{...}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">default</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">onError</span>(ChatModelErrorContext<span style="color:#bbb"> </span>errContext)<span style="color:#bbb"> </span>{...}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The various <code>*Context</code> parameters contain various details about how the model was parameterized, what the prompt was, or what error was encountered.</p>
<p>It might be interesting to follow the recent <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry GenAI recommendations</a>
and implement a listener that directly plugs into your observability solution!</p>
<h2 id="enum-structured-output">Enum structured output</h2>
<p>I&rsquo;ll finish the laundry list of features with the <strong>enum</strong> structured output.</p>
<p>The Gemini models have great support for structured output.
Not only can you ask for JSON outputs, but you can also specify a JSON schema so that the model follows that schema for generating its JSON response.
This is of utmost importance for deterministic parseable results that fit well with your strongly typed programming language.</p>
<p>Gemini lets you return arbitray JSON objects and arrays.
But for tasks like classification or sentiment analysis, it is also able to return a single enum value, rather than a JSON object that would have a property containing the value.</p>
<ul>
<li>Vertex AI Gemini:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#bbb"> </span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GCP_LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(GEMINI_1_5_PRO)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">addAllEnum</span>(Arrays.<span style="color:#4070a0">asList</span>(<span style="color:#4070a0">&#34;POSITIVE&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;NEUTRAL&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;NEGATIVE&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(asList(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Your role is to analyse the sentiment of user&#39;s messages&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;This is super exciting news, congratulations!&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// POSITIVE</span><span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>Google AI Gemini:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>GoogleAiGeminiChatModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseFormat</span>(ResponseFormat.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">type</span>(JSON)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">jsonSchema</span>(JsonSchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">rootElement</span>(JsonObjectSchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">properties</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LinkedHashMap<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>JsonSchemaElement<span style="color:#666">&gt;</span>()<span style="color:#bbb"> </span>{{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>put(<span style="color:#4070a0">&#34;sentiment&#34;</span>,<span style="color:#bbb"> </span>JsonEnumSchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">enumValues</span>(<span style="color:#4070a0">&#34;POSITIVE&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;NEUTRAL&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;NEGATIVE&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ChatResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">chat</span>(ChatRequest.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">messages</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;Your role is to analyse the sentiment of user&#39;s messages&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;This is super exciting news, congratulations!&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// POSITIVE</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>This is particularly useful for all sorts of classification tasks!</p>
<h2 id="documentation-updates">Documentation updates</h2>
<p>As I often used to say when working on the Apache Groovy project:</p>
<blockquote>
<p>&ldquo;A feature doesn&rsquo;t exist if it&rsquo;s not documented.&rdquo;</p>
<p>— Guillaume Laforge</p></blockquote>
<p>With that motto in mind, I thought it was high time that I expanded the documentation for the Gemini related pages of the LangChain4j documentation:</p>
<ul>
<li><a href="https://docs.langchain4j.dev/integrations/document-loaders/google-cloud-storage">Google Cloud Storage document loader</a></li>
<li><a href="https://docs.langchain4j.dev/integrations/scoring-reranking-models/vertex-ai">Google Cloud Ranking API</a></li>
<li><a href="https://docs.langchain4j.dev/integrations/embedding-models/google-vertex-ai">Vertex AI embedding models</a></li>
<li><a href="https://docs.langchain4j.dev/integrations/language-models/google-ai-gemini">Google AI Gemini models</a></li>
<li><a href="https://docs.langchain4j.dev/integrations/language-models/google-vertex-ai-gemini">Google Cloud Vertex AI Gemini models</a></li>
</ul>
<h2 id="tell-me-what-you-use-langchain4js-gemini-support-for">Tell me what you use LangChain4j&rsquo;s Gemini support for!</h2>
<p>I&rsquo;m really curious about what you are developing with LangChain4j, and even more so, if you&rsquo;re using the various Gemini components.
Don&rsquo;t hesitate to reach out to me, via the social media platforms mentioned below!
I&rsquo;m also interested in the features you&rsquo;d like to see prioritized and implemented.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Some advice and good practices when integrating an LLM in your application</title><link>https://glaforge.dev/posts/2024/09/23/some-good-practices-when-integrating-an-llm-in-your-application/</link><pubDate>Mon, 23 Sep 2024 17:45:17 +0200</pubDate><guid>https://glaforge.dev/posts/2024/09/23/some-good-practices-when-integrating-an-llm-in-your-application/</guid><description>&lt;p>When integrating an LLM into your applicaton to extend it and make it smarter, it&amp;rsquo;s important to be aware of the pitfalls and best practices you need to follow to avoid some common problems and integrate them successfully. This article will guide you through some key best practices that I&amp;rsquo;ve come across.&lt;/p>
&lt;h2 id="understanding-the-challenges-of-implementing-llms-in-real-world-applications">Understanding the Challenges of Implementing LLMs in Real-World Applications&lt;/h2>
&lt;p>One of the first challenges is that LLMs are constantly being improved. This means that the model you start using could change under the hood, and suddenly your application doesn&amp;rsquo;t work as it did before. Your prompts might need adjustments to work with the newer version, or worse, they might even lead to unintended results!&lt;/p></description><content:encoded>
<![CDATA[<p>When integrating an LLM into your applicaton to extend it and make it smarter, it&rsquo;s important to be aware of the pitfalls and best practices you need to follow to avoid some common problems and integrate them successfully. This article will guide you through some key best practices that I&rsquo;ve come across.</p>
<h2 id="understanding-the-challenges-of-implementing-llms-in-real-world-applications">Understanding the Challenges of Implementing LLMs in Real-World Applications</h2>
<p>One of the first challenges is that LLMs are constantly being improved. This means that the model you start using could change under the hood, and suddenly your application doesn&rsquo;t work as it did before. Your prompts might need adjustments to work with the newer version, or worse, they might even lead to unintended results!</p>
<p>Furthermore, you need to consider how to effectively manage your prompts, especially when your applications start to get more complex. Prompts can easily become convoluted and difficult to modify. Imagine having to comb through a hundred lines of code in your application to find a specific prompt just to tweak a single word. That&rsquo;s why <strong>prompt externalization</strong> (not keeping your prompts inside your source files) is going to be important, to easily find your prompts, and have a birds-eye view of all of them!</p>
<p>Keeping track of changes and ensuring that the LLM&rsquo;s behavior remains consistent throughout your development process is another challenge. How can you ensure that a particular feature still functions correctly after upgrading your prompts, or even changing model versions? You need to <strong>version your prompts</strong> (we&rsquo;ll cover that in more details in a moment). Think of your prompts like code — just like your software code, prompts should have version control for easy management. Versioning ensures that you can quickly revert to previous versions if necessary, while providing a helpful audit trail to see exactly what changes have occurred in your prompt management process.</p>
<h2 id="prompt-engineering-for-consistent-and-effective-llm-applications">Prompt Engineering for Consistent and Effective LLM Applications</h2>
<p>You&rsquo;ve probably noticed that one of the main things that determines how well your application works with a Large Language Model (LLM) is the <em>prompt</em> you use to guide it. Prompts act like a guidebook for the LLM, explaining what you expect from it and how it should format its response. You&rsquo;ve likely heard about the importance of using good prompts, but how do you go about creating prompts that are reliable and adaptable in the long run?</p>
<p>Think of your prompts like code artifacts. Just as you version your code to keep track of changes and ensure consistency, you should also version your prompts. This allows you to:</p>
<ul>
<li><strong>Keep track of your prompt evolution:</strong> You&rsquo;ll have a clear record of how your prompts have changed over time, which helps you understand the application&rsquo;s evolving behavior.</li>
<li><strong>Create a helpful audit trail:</strong> Having versions of your prompts will help you understand exactly how the application behaved at specific times. This is essential for debugging, diagnosing issues, or understanding how user feedback impacted your application.</li>
<li><strong>Enable rollbacks:</strong> If you encounter an issue or want to test different prompt versions, you can easily revert to a previous state to ensure that you can isolate problems, revert to previously working versions, or simply experiment with different phrasing.</li>
</ul>
<p>But simply versioning prompts isn&rsquo;t enough. Imagine you need to make a change to one particular prompt in a massive LLM-powered application. It might involve a lot of tedious code-hunting. That&rsquo;s where <strong>prompt externalization</strong>, that we mentioned earlier, comes in! Externalizing prompts is all about taking them out of your code and treating them like a separate configuration file. This way, they are:</p>
<ul>
<li><strong>Easy to modify:</strong> Changing your prompts becomes a breeze. Just go to your external prompt file, make the adjustments, and you&rsquo;re ready to go! No more scouring through complex code to find a single prompt in some string variables somewhere.</li>
<li><strong>More flexible:</strong> By using externalized prompts, you can easily experiment with different versions or phrasing without rewriting your entire application. This lets you quickly adapt your prompts in response to user feedback or changes in your model.</li>
<li><strong>Easier to manage:</strong> Keeping prompts in their own dedicated file makes it easy to maintain them, making sure that your prompts are consistent and up-to-date. This approach becomes increasingly valuable as your applications become more complex and you have a growing set of prompts to maintain.</li>
</ul>
<p>There are open source projects, or open formats that have emerged recently, to externalize prompts.
For examples Firebase&rsquo;s <a href="https://firebase.google.com/docs/genkit">GenKit</a> LLM framework came up with their <a href="https://firebase.google.com/docs/genkit/dotprompt">dotPrompt</a> format,
which not only externalizes the prompt itself, but also the name of the model, its configuration (temperature, etc.)</p>
<h2 id="model-versioning-preventing-surprises">Model Versioning: Preventing Surprises</h2>
<p>Now let&rsquo;s cover the importance of managing model versions, a critical aspect of ensuring that your LLM-powered application continues to work reliably.</p>
<p>Imagine that you&rsquo;ve built a great application using a particular model, and you&rsquo;re proud of the results. However, what happens when the LLM provider releases an updated version? It might offer performance improvements, but the updates can also change how the model responds to your prompts, potentially leading to unexpected issues or even breaking your application.</p>
<p>To avoid these unexpected changes, the key principle is to <strong>pin the specific version of the LLM model</strong> that you use for your application. For example, when using Gemini 1.5 Pro, if you use the version <code>gemini-1.5-pro</code>, you&rsquo;re actually using the latest version of the model. Currently, it&rsquo;s <code>gemini-1.5-pro-001</code>. But if tomorrow Google releases <code>gemini-1.5-pro-002</code>, your application would suddenly start using that new version. So be very explicit in the model version.</p>
<p>Here&rsquo;s why this is essential:</p>
<ul>
<li><strong>Avoid Drifting Model Behavior:</strong> The update to an LLM might come with subtle changes that can lead to a shift in the model&rsquo;s responses, and you may not always be able to anticipate these changes beforehand. This can lead to inconsistency, where a prompt that generated a certain output in one version of the model generates a completely different (and perhaps undesirable) output in a newer version.</li>
<li><strong>Maintain Application Consistency:</strong> To keep your application performing reliably, you want to control the LLM’s responses as much as possible, and pinning the model version ensures that you can do this. If you&rsquo;re using a specific model, the prompts that are part of your application work in the context of that model&rsquo;s specific training and behaviors. Pinning the version helps you avoid unexpected changes that may interfere with your prompts&rsquo; effectiveness.</li>
<li><strong>Simplify Auditing and Debugging:</strong> In case of an unexpected issue or an unexplained change in your LLM&rsquo;s behavior, being able to easily trace back the specific model version that&rsquo;s running provides invaluable context for debugging and understanding why those changes occurred. It helps isolate issues to specific model versions, so you can resolve them quicker.</li>
</ul>
<p>While using the latest and greatest LLM version might seem tempting for its improved capabilities, remember: <strong>the consistent performance and reliability of your application should be a top priority.</strong> By pinpointing the model version you use, you gain better control over its behavior and maintain a smooth and predictable experience for your users.</p>
<h2 id="optimizing-for-efficiency-the-power-of-response-caching">Optimizing for Efficiency: The Power of Response Caching</h2>
<p>Even with well-crafted prompts, pinned versions, generating responses from a Large Language Model (LLM) can still be expensive. This is where <strong>response caching</strong> comes in, offering a crucial way to improve both the performance and the cost-efficiency of your application.</p>
<p>Models like <a href="https://deepmind.google/technologies/gemini/">Gemini</a> support <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview">context caching</a>. Quoting the documentation:</p>
<blockquote>
<p>Use context caching to reduce the cost of requests that contain repeat content with high input token counts. Cached context items, such as a large amount of text, an audio file, or a video file, can be used in prompt requests to the Gemini API to generate output. Requests that use the same cache in the prompt also include text unique to each prompt. For example, each prompt request that composes a chat conversation might include the same context cache that references a video along with unique text that comprises each turn in the chat. The minimum size of a context cache is 32,768 tokens.</p></blockquote>
<p>By <strong>caching frequently used responses</strong> or heavy multimodal documents, you avoid having to generate them over and over again, leading to a dramatic improvement in performance and a reduction in LLM usage costs. Imagine users frequently asking the same question, like “What are the benefits of using your app?&quot;. By caching the response to this question, you&rsquo;ll be able to provide users with a fast and efficient response without burdening the LLM each time.</p>
<p>But how do you actually implement caching? You can choose different strategies for your caching system, each with its own benefits:</p>
<ul>
<li><strong>Context Caching:</strong> If your model, like Gemini, supports caching already, be sure to understand how it works, what can be cached or not, the pros and cons, or potential limitations.</li>
<li><strong>Basic Caching:</strong> Store LLM responses based on the exact input. If you encounter a query that you&rsquo;ve already generated, you can provide a pre-cached response, saving on processing time. You could also do some minimal string modifications to normalize whitespace, put everything in lowercase, etc, to get the chance to cache very similar prompts.</li>
<li><strong>Advanced Caching with Similarity Search:</strong> Even with string normalization, you might find that users don&rsquo;t always ask the exact same question, but the query can still be extremely similar. Think of typos, minor word substitutions, synonyms, or variations in punctuation. Instead of treating every query as unique, consider <strong>approximate nearest neighbor search</strong> and <strong>embedding vector similarity</strong>. This approach helps you find queries that are nearly identical, even with minor variations. You can then leverage this functionality to serve the same cached response for queries that are semantically similar, increasing the effectiveness of your caching strategy and ensuring that you only compute distinct queries once. For vector similarity, make sure to test with different inputs, to find the right threshold to say that a new prompt is equivalent to an older cached prompt/response pair.</li>
</ul>
<p>Caching responses not only speeds up your LLM-powered application, lowering the perceived latency, but also significantly cuts down on LLM usage costs, helping you keep your application running smoothly while maximizing cost-effectiveness.</p>
<h2 id="building-safeguards-ensuring-robustness-with-guardrails">Building Safeguards: Ensuring Robustness with Guardrails</h2>
<p>Let&rsquo;s shift our focus to building safety mechanisms. This is crucial for creating reliable, trustworthy applications. Enter the concept of <strong>guardrails</strong>, which are safety systems designed to protect your application and users from unexpected, unwanted, or even harmful outcomes.</p>
<p>Think of guardrails like a protective fence, ensuring that the LLM stays within safe boundaries while performing its tasks. Imagine if someone tried to make an inappropriate request, or worse, a request that could cause harm. This is where guardrails step in.</p>
<p>Guardrails serve two main purposes:</p>
<ul>
<li><strong>Input Validation:</strong> Guardrails can examine the user input and determine whether it&rsquo;s acceptable for your application and whether it aligns with your intended use case. Imagine preventing your LLM from processing prompts with malicious language or data that could cause harm to users.</li>
<li><strong>Output Filtering:</strong> Guardrails are not only for examining the user&rsquo;s input but also for checking the outputs of the LLM. By analyzing the LLM&rsquo;s generated responses, you can filter out inappropriate content or responses that don&rsquo;t meet your requirements.</li>
</ul>
<p>What are the three primary types of guardrails?</p>
<ul>
<li><strong>Model&rsquo;s safety settings:</strong> Models have usually been fine tuned to avoid certain harmful content in both input and output. They also give you access to safety settings, with different harm categories and safety thresholds. You should test those settings and how they can be configured for your use case. For example, have a look at the available <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters">safety filters</a> for Gemini.</li>
<li><strong>Static Guardrails:</strong> These are predefined rules that are set before the LLM begins to process any input. These can be rules that dictate certain formatting for prompts, limitations on input length, or even basic checks for prohibited terms or requests. Static guardrails offer fast processing times, since the checks are performed beforehand on the input strings, in your own code.</li>
<li><strong>Dynamic Guardrails:</strong> These are flexible guardrails that work in conjunction with the LLM or the embedding model used for text classification, continuously adapting to changes in user input or the output of the model itself. They allow you to handle complex or unpredictable situations and perform nuanced checks to maintain the safety and integrity of your application. You might have a look at Google Cloud Natural Language Processing&rsquo;s <a href="https://cloud.google.com/natural-language/docs/moderating-text">moderation endpoint</a>, or the free <a href="https://perspectiveapi.com/">Perspective API</a> used by newspapers.</li>
</ul>
<p>When you implement guardrails, it&rsquo;s also critical to consider performance impact. You want to make sure that these safeguards don&rsquo;t add too much latency and delay user experience. That&rsquo;s where <strong>parallel processing</strong> can come into play! Instead of waiting for the guardrail check to finish before starting the LLM generation, consider launching both tasks in parallel, optimizing speed and efficiency without sacrificing safety. Then, if the guardrails raise a red flag, you can stop the response generation, and reply right away to the user that the input content was problematic. For the response, unless you have a streaming kind of guardrail system, you might have to wait for the whole response to be generated before evaluating it with the guardrail, in which case, you can&rsquo;t really do parallel processing.</p>
<p>Always remember: Guardrails should be continuously refined and updated as you identify new potential risks. Gathering feedback from users, giving them the ability to report a bad response is one approach. But you should also monitor your application LLM responses to do some vibe-checks at random to ensure your application is behaving correctly.</p>
<h2 id="evaluating-and-monitoring-for-consistent-performance">Evaluating and Monitoring for Consistent Performance</h2>
<p>Onto the most crucial aspects of any application, regardless of its technology, is <strong>evaluation and monitoring</strong>. This is essential for ensuring your LLM-powered application continues to function reliably and meets your expectations as it interacts with users in the real world.</p>
<p>Imagine you make an update to your application, or perhaps even a simple tweak to one of your prompts. Without proper monitoring, you won&rsquo;t know if those changes had unintended consequences. You could end up with an app that gives unexpected results, leads to user frustration, or even creates unforeseen safety issues. That&rsquo;s where a robust evaluation and monitoring framework comes into play!</p>
<p>Your LLM-powered app needs a systematic way to ensure that everything is running smoothly and effectively. You need to:</p>
<ul>
<li>
<p><strong>Establish Evaluation Metrics:</strong> You need clear guidelines to judge the LLM&rsquo;s performance. Think of key metrics like accuracy, relevance, and coherence.</p>
<ul>
<li><strong>Accuracy:</strong> This measures how often the LLM generates correct and factually accurate responses. This is particularly crucial if your application is designed for providing reliable information or carrying out fact-based tasks.</li>
<li><strong>Relevance:</strong> You need to make sure the LLM stays focused on the core issue. It should respond to your prompts in a meaningful and helpful way, instead of giving irrelevant or off-topic responses.</li>
<li><strong>Coherence:</strong> You need to check if the LLM produces well-written and logical text. Coherent responses are easily understood by users and don&rsquo;t leave them feeling confused or disoriented.</li>
</ul>
</li>
<li>
<p><strong>Gather User Feedback:</strong> It&rsquo;s essential to go beyond just numbers. Your application&rsquo;s performance shouldn&rsquo;t just be evaluated on your own terms. Get feedback from the users, gather data on how they are using the application, and check their satisfaction with the outputs of your application. You can even ask users to provide their opinions on specific generated answers, giving you valuable insights into what resonates with them and how you can improve. Consider using tools like &ldquo;thumbs up&rdquo; or &ldquo;thumbs down&rdquo; buttons, offering an easy way for users to indicate their sentiment towards the LLM&rsquo;s responses, or a way to report and explain what wasn&rsquo;t up to the level of their expectations.</p>
</li>
<li>
<p><strong>Build a “Golden Responses” Dataset:</strong> Create a collection of carefully chosen inputs and their desired, accurate responses. These “golden” examples act like benchmarks, helping you measure how closely the LLM matches your expected results for specific tasks. By periodically checking how your LLM performs against these golden examples, you can get a clear picture of potential issues and make necessary adjustments. You can use this set as a starting point to track potential regressions and make sure the LLM&rsquo;s behavior is aligned with your expectations.</p>
</li>
<li>
<p><strong>Implement Continuous Monitoring:</strong> Monitoring shouldn&rsquo;t be a one-time event. It&rsquo;s an ongoing process, like keeping a watchful eye on your application as it functions in the real world. By monitoring in real-time, you can detect anomalies, unexpected issues, or performance regressions promptly. It allows you to address these issues before they cause significant problems for your users. Maybe checkout the recent <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry guidelines for Gen AI</a> to observe how your system and LLM are performing live.</p>
</li>
</ul>
<p>You can further improve your LLM-powered application by analyzing the user&rsquo;s requests and responses generated by the LLM, especially those flagged by users as problematic or unexpected. These can be added to your collection of golden responses, constantly refining the process of evaluation. This helps your application evolve based on real-world interactions.</p>
<h2 id="addressing-data-privacy-concerns">Addressing Data Privacy Concerns</h2>
<p>Another important topic to keep in mind: <strong>data privacy</strong>. LLMs have access to a vast amount of text data, which makes them incredibly powerful. But this same power brings with it the responsibility of safeguarding sensitive information. If your application handles user data, you need to ensure that you&rsquo;re handling it with utmost care, protecting it from unauthorized access and ensuring that you comply with relevant privacy regulations.</p>
<p>Think of data privacy as a trust contract. You, as the developer, are entrusted with safeguarding the sensitive information of your users. It&rsquo;s your responsibility to implement measures that keep this data secure and prevent breaches or misuse.</p>
<p>Here are some key steps to address data privacy concerns in your LLM application:</p>
<ul>
<li><strong>Implement strong security measures:</strong> Use robust encryption methods to secure your application and data. Employ security best practices such as access controls, secure storage, and secure communication channels.</li>
<li><strong>Stay aligned with data privacy regulations:</strong> Comply with relevant privacy regulations like GDPR, CCPA, and HIPAA. You might need to review your data handling policies and make necessary adjustments.</li>
<li><strong>Ensure data anonymization:</strong> When working with sensitive data, always strive to anonymize or pseudonymize it to the fullest extent possible. You can utilize techniques like differential privacy, aggregation, or removing identifying details to protect user information (with <a href="https://cloud.google.com/security/products/dlp">Google Cloud Data Loss Prevention</a> API for example).</li>
<li><strong>Be transparent with users:</strong> Communicate clearly with your users about how you collect, use, and store their data. Offer users options to control their data, and provide mechanisms to update or delete their information if needed.</li>
</ul>
<p>By prioritizing data privacy in your LLM application, you not only uphold ethical standards but also build trust with your users. Your users should be confident that their information is being handled with respect and care, encouraging long-term trust in your application.</p>
<h2 id="tailoring-llms-for-specific-business-goals">Tailoring LLMs for Specific Business Goals</h2>
<p>Remember that LLMs are tools, and the success of your LLM application ultimately hinges on aligning its capabilities with your unique goals and your target audience. So, how do you get the most out of an LLM in your business?</p>
<p><strong>First, define your goals.</strong> What specific tasks can an LLM help you accomplish? What pain points are you trying to solve? Once you understand the big picture, you can break down those goals into actionable tasks that the LLM can potentially assist with.</p>
<p><strong>Then, it&rsquo;s time to find the right LLM for the job.</strong> Not all LLMs are created equal. Different models excel at specific tasks, have varying levels of language support, and even require different levels of computational resources. For example, if your business uses many different languages, you’ll want an LLM with multilingual support.</p>
<p>To select the best LLM for your needs, ask yourself:</p>
<ul>
<li><strong>What specific task does this LLM need to perform?</strong> Different LLMs excel at different tasks like text generation, summarization, or translation.</li>
<li><strong>How does the LLM&rsquo;s accuracy compare with the level of accuracy required for your application?</strong> The model needs to generate results with the appropriate level of precision for your goals.</li>
<li><strong>How much computational power does it need to run this LLM?</strong> Consider your budget and available infrastructure when making this selection, when hosting the model on your own. A cloud hosted model might be better (and cheaper) depending on your usage patterns, and if you don&rsquo;t want the hassle to handle your own infrastructure and GPUs.</li>
<li><strong>What language capabilities does the LLM offer?</strong> Is the model good at the languages you need to use, or are there specific domains where the model is particularly strong? It&rsquo;s not just about spoken languages, with code as well, some models maybe better dealing with a particular programming language than another one.</li>
</ul>
<p>You can often find models with specialized skills. You may find, for example, a model trained on scientific papers if your work requires the processing of highly technical content, or a model trained on a particular field, such as text of laws, to be highly effective in that domain.</p>
<p>Once you’ve chosen your LLM, the next step could be <strong>fine-tuning</strong>, where you’d tailor the model to your specific needs. It’s like customizing a tool to do the exact job you need it to do. For example, imagine your application is helping people book vacations. You can train the model on a massive amount of vacation-related text data so it can accurately understand and respond to vacation-specific questions, making your application highly relevant for its intended purpose. But fine-tuning is not necessarily for the faint of heart, and can be complicated to do right.</p>
<p>While choosing and fine-tuning are critical steps, <strong>assessing potential risks is equally important.</strong> Think about potential unintended consequences. LLMs, for example, might not always be factual or accurate in their responses. You&rsquo;ll need to find ways to manage those potential issues, often incorporating guardrails to mitigate potential harms or biases, or implementing techniques like Retrieval Augmented Generation to ground the model&rsquo;s responses on your own data and documents.</p>
<p>Ultimately, you&rsquo;ll want to make your application a tool that not only works reliably but also gives real value to your business. By understanding your business goals, choosing the right model, customizing it effectively, and understanding the potential risks, you’re on the right path to success!</p>
<h2 id="looking-ahead-emerging-trends-and-future-directions">Looking Ahead: Emerging Trends and Future Directions</h2>
<p>Remember that this field is constantly changing! New capabilities are emerging, and existing models are getting even smarter and more efficient. This is an exciting time to be working with LLMs because the possibilities feel endless!</p>
<p>While it&rsquo;s fantastic to get your application off the ground using the latest LLMs, it&rsquo;s equally important to be open to continuous improvement. What&rsquo;s great today may not be optimal in the future. The world of LLMs is one where ongoing development is key! Here are a few tips:</p>
<ul>
<li><strong>Embrace continuous learning.</strong> You should always be seeking out information about the newest developments in the field, how LLMs are being enhanced, and the impact those changes could have on your applications. Look out for improvements to existing models, new LLM models coming out, and fresh research.</li>
<li><strong>Think ahead.</strong> What new features could you integrate in your application that take advantage of those advancements? Would your app benefit from a specific, task-oriented model that focuses on summarization, question answering, or code generation? Maybe there&rsquo;s a model out there that will significantly boost performance and help you offer a smoother, more feature-rich experience for your users!</li>
<li><strong>Prepare for evolution.</strong> Remember that LLMs aren’t static! Your app should be built with a framework for easy adaptation. Consider how you can adapt to model updates or new model releases in a structured way, perhaps by putting in place frameworks for incorporating new models seamlessly and managing prompt changes for various models, like <a href="https://docs.langchain4j.dev/">LangChain4j</a> (if you&rsquo;re a Java developer) which offers higher-level abstractions and that allows you to switch models easily.</li>
</ul>
<p>The landscape of LLMs is evolving rapidly. Stay up-to-date with the latest developments and ensure your applications can adapt, allowing you to unlock the full potential of LLMs for your business!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>New Gemini model in LangChain4j</title><link>https://glaforge.dev/posts/2024/09/05/new-gemini-model-in-langchain4j/</link><pubDate>Thu, 05 Sep 2024 22:42:38 +0200</pubDate><guid>https://glaforge.dev/posts/2024/09/05/new-gemini-model-in-langchain4j/</guid><description>&lt;p>A new version of &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>, the super powerful LLM toolbox for Java developers, was released today.
In &lt;a href="https://github.com/langchain4j/langchain4j/releases">0.34.0&lt;/a>, a new Gemini model has been added.
This time, this is not the Gemini flavor from Google Cloud Vertex AI, but the &lt;a href="https://ai.google.dev/gemini-api/">Google AI&lt;/a> variant.&lt;/p>
&lt;p>It was a frequently requested feature by LangChain4j users,
so I took a stab at developing a new chat model for it, during my summer vacation break.&lt;/p>
&lt;h2 id="gemini-show-me-the-code">Gemini, show me the code!&lt;/h2>
&lt;p>Let&amp;rsquo;s dive into some code examples to see it in action!&lt;/p></description><content:encoded>
<![CDATA[<p>A new version of <a href="https://docs.langchain4j.dev/">LangChain4j</a>, the super powerful LLM toolbox for Java developers, was released today.
In <a href="https://github.com/langchain4j/langchain4j/releases">0.34.0</a>, a new Gemini model has been added.
This time, this is not the Gemini flavor from Google Cloud Vertex AI, but the <a href="https://ai.google.dev/gemini-api/">Google AI</a> variant.</p>
<p>It was a frequently requested feature by LangChain4j users,
so I took a stab at developing a new chat model for it, during my summer vacation break.</p>
<h2 id="gemini-show-me-the-code">Gemini, show me the code!</h2>
<p>Let&rsquo;s dive into some code examples to see it in action!</p>
<p>But first, you&rsquo;ll need an API key.
So just follow the instructions to <a href="https://ai.google.dev/gemini-api/docs/api-key">obtain your Gemini API key</a>.
I&rsquo;ve saved mine in the <code>GEMINI_AI_KEY</code> environment variable,
so that I don&rsquo;t have to hardcode it in my source files.</p>
<p>The code examples below have been compiled with Java 21.</p>
<p>I&rsquo;ve imported the following libraries in my build tool:</p>
<ul>
<li><code>dev.langchain4j:langchain4j-google-ai-gemini:0.34.0</code></li>
<li><code>dev.langchain4j:langchain4j-core:0.34.0</code></li>
<li><code>dev.langchain4j:langchain4j:0.34.0</code></li>
</ul>
<h3 id="lets-be-polite-and-say-hello">Let&rsquo;s be polite and say hello</h3>
<p>My mom always told me to be polite and to say hello:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Konnichiwa Gemini!&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Gemini&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Gemini&gt; Konnichiwa! It&#39;s nice to hear from you.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//         What can I do for you today?</span><span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="dont-you-like-strawberries">Don&rsquo;t you like strawberries?</h3>
<p>In the first example, I used the usual <code>generate()</code> method to send my greeting to Gemini.
But LangChain4j 0.34 introduces some new signatures and classes to interact with an LLM:</p>
<ul>
<li><code>ChatRequest</code>: a new class that contains your conversation messages,
the tools this request can use, and a response format definition to decide
what should be the shape of the output</li>
<li><code>ChatResponse</code>: this class holds the LLM&rsquo;s response, the token usage information,
and the <em>finish</em> reason (ie. if the response was cut, filtered, or was generated till the end)</li>
<li><code>ChatResponse chat(ChatRequest req)</code>: this new method is added to the LLM contract to interact with it.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ChatResponse<span style="color:#bbb"> </span>chatResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">chat</span>(ChatRequest.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">messages</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;How many R&#39;s are there in the word &#39;strawberry&#39;?&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatResponse.<span style="color:#4070a0">aiMessage</span>().<span style="color:#4070a0">text</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Gemini&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Gemini&gt; There are **three** R&#39;s in the word &#34;strawberry&#34;.</span><span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="lets-roll-the-json-dice">Let&rsquo;s roll the JSON dice!</h3>
<p>Both Gemini 1.5 Flash and Pro allow you to specify that the output should be valid JSON.
It&rsquo;s sometimes called the <strong>JSON mode</strong>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>roll<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Roll a 6-sided dice&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(roll);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// {&#34;roll&#34;: &#34;3&#34;}</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini will always reply with valid JSON structures.</p>
<p>Here, the JSON object key is not always <code>roll</code>, and is sometimes <code>die</code>, <code>dice_roll</code>, etc.
But you could tweak your prompt to ask for a specific key name.</p>
<p>Gemini follows the instructions very precisely, but it&rsquo;s not guaranteed 100% that it will really use the requested key name.
But fear not, there&rsquo;s an even more powerful solution, thanks to response formats!</p>
<h3 id="lets-cook-something-with-our-strawberries">Let&rsquo;s cook something with our strawberries</h3>
<p>You can configure Gemini to make it generate outputs that comply with a JSON schema.
It&rsquo;s sometimes called <strong>controlled generation</strong>, or <strong>constrained decoding</strong>.</p>
<p>Let&rsquo;s say we have a schema that represents recipes!
It&rsquo;s time to do something with our strawberries!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(JsonSchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">rootElement</span>(JsonObjectSchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">properties</span>(Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;title&#34;</span>,<span style="color:#bbb"> </span>JSON_STRING_SCHEMA,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;preparationTimeMinutes&#34;</span>,<span style="color:#bbb"> </span>JSON_INTEGER_SCHEMA,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;ingredients&#34;</span>,<span style="color:#bbb"> </span>JsonArraySchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">items</span>(JSON_STRING_SCHEMA)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">build</span>(),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#4070a0">&#34;steps&#34;</span>,<span style="color:#bbb"> </span>JsonArraySchema.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">items</span>(JSON_STRING_SCHEMA)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>recipeResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;Suggest a dessert recipe with strawberries&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(recipeResponse);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">/*
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">{&#34;ingredients&#34;: [&#34;1 pint fresh strawberries, hulled and sliced&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;1/2 cup sugar&#34;, &#34;1/4 cup water&#34;, &#34;1 tablespoon lemon juice&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;1/2 teaspoon vanilla extract&#34;, &#34;1 cup heavy cream, whipped&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;preparationTimeMinutes&#34;: 30, &#34;steps&#34;: [&#34;In a saucepan, combine
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">the sugar, water, and lemon juice. Bring to a boil over medium
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">heat, stirring until the sugar is dissolved.&#34;, &#34;Reduce the heat
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">to low and simmer for 5 minutes, or until the syrup thickens
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">slightly.&#34;, &#34;Remove from heat and stir in the vanilla extract.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Pour the syrup over the strawberries in a bowl and stir to coat.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Refrigerate for at least 30 minutes, or until chilled.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;To serve, top the strawberries with whipped cream and enjoy!&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;title&#34;: &#34;Strawberry Shortcake&#34;}
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"> */</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Gemini strictly follows the specified JSON schema, and generates a JSON object that matches.</p>
<p>This is particularly important when you integrate LLMs in your application.
You want a deterministic format for the output that can easily be parsed and handled by your system.</p>
<h3 id="tasty-strawberries-from-japan">Tasty strawberries from Japan!</h3>
<p>A few months back, I had the chance to visit Japan with my family,
and they have some really gorgeous and tasty strawberries there!
And don&rsquo;t get me started on strawberry daifukus (mochis with fruits inside) we had in Osaka!</p>
<p>But before tasting those lovely confections, we need to plan our trip to Japan.</p>
<p>In the previous example, you might have found that a bit painful to describe the JSON schema.
For the integration in a Java application, you might have some more complex data structures to represent,
so deriving the big schema to define them can be tedious.
Fortunately, there&rsquo;s a little trick to get the JSON schema for a Java class (or record, enum, etc.).</p>
<p>Let&rsquo;s define and describe our trip itinerary object:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;details of a trip itinerary&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">TripItinerary</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>country,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Integer<span style="color:#bbb"> </span>numberOfPersons,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Month<span style="color:#bbb"> </span>month,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;key highlights when visiting the city&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List<span style="color:#666">&lt;</span>CityHighlights<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>cityHighlights<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">enum</span><span style="color:#bbb"> </span>Month<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>JANUARY,<span style="color:#bbb"> </span>FEBRUARY,<span style="color:#bbb"> </span>MARCH,<span style="color:#bbb"> </span>APRIL,<span style="color:#bbb"> </span>MAY,<span style="color:#bbb"> </span>JUNE,<span style="color:#bbb"> </span>JULY,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>AUGUST,<span style="color:#bbb"> </span>SEPTEMBER,<span style="color:#bbb"> </span>OCTOBER,<span style="color:#bbb"> </span>NOVEMBER,<span style="color:#bbb"> </span>DECEMBER<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">CityHighlights</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>cityName,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>List<span style="color:#666">&lt;</span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>visitHighlights<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The itinerary is represented by some records, enums, and lists,
and the <code>@Description</code> annotation can help the LLM to better understand what some elements might be about
(in particular when you have some cryptic field names, but here,
it&rsquo;s not strictly necessary as Gemini is smart enough to understand what each field is about)</p>
<p>Now let&rsquo;s ask for our Japan itinerary:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">temperature</span>(2.<span style="color:#4070a0">0</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(jsonSchemaFrom(TripItinerary.<span style="color:#4070a0">class</span>).<span style="color:#4070a0">get</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We derive the JSON schema from the <code>TripItinerary</code> class.
No need to tediously craft a JSON schema for it.</p>
<p>Let&rsquo;s see what Gemini suggests for our visit:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>tripResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;You are an expert trip planner&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Suggest an itinerary for Japan.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Cities visited: Tokyo, Kyoto, Osaka.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Trip for a family of 4 persons.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Provide key highlights for each city visited.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>We tell Gemini to act as an expert trip planner, and we give some details about the travellers, the cities we&rsquo;d like to visit.</p>
<p>So what&rsquo;s the JSON structured output for this Japan visit?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(tripResponse);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">/*
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">{&#34;cityHighlights&#34;: [{&#34;cityName&#34;: &#34;Tokyo&#34;, &#34;visitHighlights&#34;:
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">[&#34;Explore the vibrant Shibuya Crossing and the iconic Shibuya
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Scramble.&#34;, &#34;Visit the Meiji Jingu Shrine, a serene oasis in
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">the heart of Tokyo.&#34;, &#34;Experience the fascinating world of
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">technology at the Miraikan National Museum of Emerging Science
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">and Innovation.&#34;, &#34;Enjoy a traditional tea ceremony at one of
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Tokyo&#39;s many teahouses.&#34;, &#34;Get lost in the eclectic streets
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">of Harajuku and admire the unique fashion styles.&#34;, &#34;Embark
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">on a scenic boat trip on the Sumida River, passing by Tokyo
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Skytree.&#34;, &#34;Indulge in a delightful sushi dinner at a renowned
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Tsukiji Fish Market.&#34;, &#34;Discover the charm of Ueno Park, home
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">to museums, temples, and the Ueno Zoo.&#34;]}, {&#34;cityName&#34;: &#34;Kyoto&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;visitHighlights&#34;: [&#34;Wander through the serene gardens of the
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Golden Pavilion (Kinkaku-ji).&#34;, &#34;Immerse yourselves in the rich
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">history of the Kiyomizu-dera Temple, famous for its wooden stage.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Explore the ancient Gion district, known for its traditional
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">wooden buildings and geisha houses.&#34;, &#34;Stroll through the Fushimi
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Inari Shrine, famous for its thousands of red torii gates.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Discover the treasures of the Nishiki Market, offering a diverse
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">selection of food and crafts.&#34;, &#34;Experience a traditional geisha
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">performance at one of Kyoto&#39;s exclusive theaters.&#34;, &#34;Learn the
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">art of calligraphy at a traditional workshop in the Gion district.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Relax in the serene atmosphere of the Ryoan-ji Zen Garden.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Witness the beauty of the Arashiyama Bamboo Grove.&#34;]},
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">{&#34;cityName&#34;: &#34;Osaka&#34;, &#34;visitHighlights&#34;: [&#34;Experience the vibrant
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Dotonbori district, renowned for its neon lights, street food,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">and entertainment.&#34;, &#34;Explore the Osaka Castle, a historic
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">landmark and symbol of the city.&#34;, &#34;Enjoy the breathtaking
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">panoramic views from the Abeno Harukas, Japan&#39;s tallest
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">skyscraper.&#34;, &#34;Visit the Osaka Aquarium Kaiyukan, home to diverse
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">marine life from around the world.&#34;, &#34;Stroll through the lively
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Kuromon Market, known for its fresh seafood and local produce.&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;Take a scenic ride on the Osaka Ferris Wheel, offering views of
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">the cityscape.&#34;, &#34;Indulge in the delicious okonomiyaki, Osaka&#39;s
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">signature dish.&#34;, &#34;Experience the unique culture of the Sumiyoshi
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">Taisha Shrine, dedicated to the gods of seafaring.&#34;]}],
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">&#34;country&#34;: &#34;Japan&#34;, &#34;month&#34;: &#34;MARCH&#34;, &#34;numberOfPersons&#34;: 4}
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"> */</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Damn! It didn&rsquo;t even mention the most delicious daifukus we had in Osaka!</p>
<h3 id="can-i-go-outside-without-my-umbrella-in-osaka-tonight">Can I go outside without my umbrella in Osaka, tonight?</h3>
<p>Speaking of visiting Osaka and those great daifukus, what&rsquo;s the weather like there?
It&rsquo;s been raining a lot in Paris today, so I&rsquo;m curious if it&rsquo;s better in Osaka.</p>
<p>This new Gemini chat model works with LangChain4j&rsquo;s higher-level abstractions: <code>AiServices</code>,
to create some very powerful LLM based apps, like smart agents or RAG (Retrieval Augmented Generation).</p>
<p>We&rsquo;ll have a look at a great use case for LLMs: <strong>data extraction from free-form text</strong>.</p>
<p>Let&rsquo;s define and describe a weather forecast record:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">WeatherForecast</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;minimum temperature&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Integer<span style="color:#bbb"> </span>minTemperature,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;maximum temperature&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Integer<span style="color:#bbb"> </span>maxTemperature,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Description</span>(<span style="color:#4070a0">&#34;chances of rain&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">boolean</span><span style="color:#bbb"> </span>rain<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;ll also create an interface for our weather service contract:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">WeatherForecastAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WeatherForecast<span style="color:#bbb"> </span><span style="color:#06287e">extract</span>(String<span style="color:#bbb"> </span>forecast);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s configure Gemini, instantiate our weather assistant,
and extract the weather forecast from today&rsquo;s newspaper:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>WeatherForecastAssistant<span style="color:#bbb"> </span>forecastAssistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiServices.<span style="color:#4070a0">builder</span>(WeatherForecastAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatLanguageModel</span>(gemini)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>WeatherForecast<span style="color:#bbb"> </span>forecast<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>forecastAssistant.<span style="color:#4070a0">extract</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Morning: The day dawns bright and clear in Osaka, with crisp
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    autumn air and sunny skies. Expect temperatures to hover
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    around 18°C (64°F) as you head out for your morning stroll
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    through Namba.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Afternoon: The sun continues to shine as the city buzzes with
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    activity. Temperatures climb to a comfortable 22°C (72°F).
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Enjoy a leisurely lunch at one of Osaka&#39;s many outdoor cafes,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    or take a boat ride on the Okawa River to soak in the beautiful
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    scenery.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Evening: As the day fades, expect clear skies and a slight chill
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    in the air. Temperatures drop to 15°C (59°F). A cozy dinner at a
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    traditional Izakaya will be the perfect way to end your day in
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Osaka.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Overall: A beautiful autumn day in Osaka awaits, perfect for
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    exploring the city&#39;s vibrant streets, enjoying the local cuisine,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    and soaking in the sights.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Don&#39;t forget: Pack a light jacket for the evening and wear
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    comfortable shoes for all the walking you&#39;ll be doing.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Gemini&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>forecast);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Gemini&gt; WeatherForecast[</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//             minTemperature=15,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//             maxTemperature=22,</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//             rain=false]</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Awesome, no need for my umbrella!</p>
<p>What&rsquo;s great here is that we&rsquo;re dealing with a real type-safe Java object, not JSON strings like before.
So it integrates very well within our Java codebase!</p>
<h3 id="time-for-a-little-coding-quiz">Time for a little coding quiz</h3>
<p>Alright, after the touristic detour, let&rsquo;s get back to some math, and some coding.
LLMs are quite good at reasoning, in particular when you encourage them to think <em>step by step</em>.
But sometimes, they fall short, and can&rsquo;t really calcuate results.
They&rsquo;re language models, not calculators, right?</p>
<p>Gemini has the ability to create some Python scripts, and to execute them in a sandbox.
So how can we configure Gemini for solving a little math problem?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">allowCodeExecution</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">includeCodeExecutionOutput</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 2 builder methods:</p>
<ul>
<li><code>allowCodeExecution(true)</code>: to let Gemini know it can do some Python coding</li>
<li><code>includeCodeExecutionOutput(true)</code>: if you want to see the actual Python script it came up with, and the output of its execution</li>
</ul>
<p>Do you know off head how much is <code>fibonacci(22)</code> or <code>ackermann(3, 4)</code>?
Let&rsquo;s ask Gemini:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>mathQuizz<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are an expert mathematician.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When asked a math problem or logic problem,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        you can solve it by creating a Python program,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        and execute it to return the result.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Implement the Fibonacci and Ackermann functions.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        What is the result of `fibonacci(22)` - ackermann(3, 4)?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Looks like Gemini is a Python and math wiz:</p>
<pre tabindex="0"><code>Code executed:
```python
def fibonacci(n):
    if n &lt;= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

def ackermann(m, n):
    if m == 0:
        return n + 1
    elif n == 0:
        return ackermann(m - 1, 1)
    else:
        return ackermann(m - 1, ackermann(m, n - 1))

print(fibonacci(22) - ackermann(3, 4))
```
Output:
```
17586
```
The result of `fibonacci(22) - ackermann(3, 4)` is **17586**.

I implemented the Fibonacci and Ackermann functions in Python.
Then I called `fibonacci(22) - ackermann(3, 4)` and printed the result.
</code></pre><p>If you don&rsquo;t include the script code and output, you would receive only the end of the message:</p>
<pre tabindex="0"><code>The result of `fibonacci(22) - ackermann(3, 4)` is **17586**.

I implemented the Fibonacci and Ackermann functions in Python.
Then I called `fibonacci(22) - ackermann(3, 4)` and printed the result.
</code></pre><p>I didn&rsquo;t encounter any snake in Japan, but I&rsquo;m happy Gemini can write some Python functions when needed!</p>
<h3 id="what-about-the-weather-in-tokyo">What about the weather in Tokyo?</h3>
<p>Besides this Python code execution sandbox, the more traditional <strong>function calling</strong> mechanism works.
We heard about the weather in Osaka, now let&rsquo;s ask for Tokyo.</p>
<p>Let&rsquo;s define a <em>tool</em> to retrieve structured weather forecasts:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">WeatherForecast</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>location,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>forecast,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>temperature)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">WeatherForecastService</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Get the weather forecast for a location&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WeatherForecast<span style="color:#bbb"> </span><span style="color:#06287e">getForecast</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#555;font-weight:bold">@P</span>(<span style="color:#4070a0">&#34;Location to get the forecast for&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>location)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(location.<span style="color:#4070a0">equals</span>(<span style="color:#4070a0">&#34;Paris&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WeatherForecast(<span style="color:#4070a0">&#34;Paris&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;sunny&#34;</span>,<span style="color:#bbb"> </span>20);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(location.<span style="color:#4070a0">equals</span>(<span style="color:#4070a0">&#34;London&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WeatherForecast(<span style="color:#4070a0">&#34;London&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;rainy&#34;</span>,<span style="color:#bbb"> </span>15);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(location.<span style="color:#4070a0">equals</span>(<span style="color:#4070a0">&#34;Tokyo&#34;</span>))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WeatherForecast(<span style="color:#4070a0">&#34;Tokyo&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;warm&#34;</span>,<span style="color:#bbb"> </span>32);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WeatherForecast(<span style="color:#4070a0">&#34;Unknown&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;unknown&#34;</span>,<span style="color:#bbb"> </span>0);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We need a weather forecast assistant as well, that we&rsquo;ll instantiate and configure with our tool, thanks to <code>AiServices</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">WeatherAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">chat</span>(String<span style="color:#bbb"> </span>userMessage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>WeatherForecastService<span style="color:#bbb"> </span>weatherForecastService<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>WeatherForecastService();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">temperature</span>(0.<span style="color:#4070a0">0</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>WeatherAssistant<span style="color:#bbb"> </span>weatherAssistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiServices.<span style="color:#4070a0">builder</span>(WeatherAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatLanguageModel</span>(gemini)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">tools</span>(weatherForecastService)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>tokyoWeather<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>weatherAssistant.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;What is the weather forecast for Tokyo?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Gemini&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>tokyoWeather);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// Gemini&gt; The weather forecast for Tokyo is warm</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//         with a temperature of 32 degrees.</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>I didn&rsquo;t expect such warm temperatures in Tokyo!
Well, of course, it&rsquo;s all fake, but you can imagine calling a real weather service.</p>
<p>The beauty of LangChain4j&rsquo;s <code>AiServices</code> is that it handles calling the service for you.
Usually, with function calling, the LLM just replies with a request that says <em>you</em> should be calling a tool or API,
and give it back the tool/API&rsquo;s answer.
Here, with <code>AiServices</code>, it&rsquo;s all automatic and transparent.</p>
<h3 id="of-parrots-pictures-text-files-and-multimodality">Of parrots pictures, text files, and multimodality</h3>
<p>Let&rsquo;s finish our whirlwind tour of this Google AI Gemini model for LangChain4j
with an example that highlights <strong>Gemini&rsquo;s multimodal capabilities</strong>.</p>
<p>Gemini is a multimodal LLM: in input, in addition to text, it accepts pictures, videos, audio, PDF files, and text files.</p>
<p>Let&rsquo;s ask what Gemini thinks of the cute colorful parrot mascot of LangChain4j.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// README.md markdown file from LangChain4j&#39;s project Github repos</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>base64Text<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>b64encoder.<span style="color:#4070a0">encodeToString</span>(readBytes(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#4070a0">&#34;https://github.com/langchain4j/langchain4j/blob/main/README.md&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// PNG of the cute colorful parrot mascot of the LangChain4j project</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>base64Img<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>b64encoder.<span style="color:#4070a0">encodeToString</span>(readBytes(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#4070a0">&#34;https://avatars.githubusercontent.com/u/132277850?v=4&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ChatLanguageModel<span style="color:#bbb"> </span>gemini<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GEMINI_AI_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>gemini.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextFileContent.<span style="color:#4070a0">from</span>(base64Text,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;text/x-markdown&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ImageContent.<span style="color:#4070a0">from</span>(base64Img,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;image/png&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            Do you think this logo fits well
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            with the project description?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Gemini&gt; &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">/*
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">   Gemini&gt; The logo of a parrot drinking tea doesn&#39;t seem like a
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">   good fit for a project description of a Java version of LangChain.
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">   It&#39;s not clear how the logo relates to the project&#39;s purpose or
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">   functionality. A logo that better reflects the project&#39;s technical
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">   nature, such as a stylized representation of code or a language
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">   model, would be more appropriate.
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"> */</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Ah well, looks like LLM don&rsquo;t want to be compared to <em>stochastic parrots</em>,
so it thinks the parrot mascot doesn&rsquo;t represent the project well enough!</p>
<p>Sorry Gemini, I have to disagree, I really love this logo!</p>
<p>This example shows that you can craft an elaborate prompt that contains a text query,
an external text file (the description of the project in Markdown format),
and the picture of the parrot mascot.</p>
<h2 id="lets-wrap-up--with-beautiful-furoshiki-fabric">Let&rsquo;s wrap up — with beautiful &lsquo;furoshiki&rsquo; fabric!</h2>
<p>Throughout this journey through code examples, strawberries, daifukus, Japan itineraries and weather forecasts,
you learned about the brand new LangChain4j module for Google AI&rsquo;s Gemini API, and its capabilities.
I hope this article makes you want to try it out!</p>
<p>Before calling it a day or night (depending on your timezone), I&rsquo;d like to mention some limitations,
as it&rsquo;s still early days for this new module:</p>
<ul>
<li>Currently, there&rsquo;s only a <code>ChatLanguageModel</code> available,
but no <code>StreamingChatLanguageModel</code> class, so you won&rsquo;t get streamed responses yet.</li>
<li>Gemini&rsquo;s content caching capability is not surfaced in this implementation,
so you can&rsquo;t use caching to save some bucks or yens.</li>
<li>For multimodality, you should pass the Base64 encoded bytes of the files, and not use URLs to reference those resources,
as this module doesn&rsquo;t yet upload files to Gemini&rsquo;s file service (Gemini won&rsquo;t download from an external URL).</li>
</ul>
<p>Hopefully, the community will adopt this module, work with it, provide feedback to help us improve it further!
Don&rsquo;t hesitate to reach out with questions or to report any problems you encounter.
And if you build something cool, please tell me too!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A retryable JUnit 5 extension for flaky tests</title><link>https://glaforge.dev/posts/2024/09/01/a-retryable-junit-5-extension/</link><pubDate>Sun, 01 Sep 2024 16:00:51 +0200</pubDate><guid>https://glaforge.dev/posts/2024/09/01/a-retryable-junit-5-extension/</guid><description>&lt;p>As I work a lot with Large Language Models (LLMs), I often have to deal with flaky test cases,
because LLMs are not always consistent and deterministic in their responses.
Thus, sometimes, a test passes maybe a few times in a row, but then, once in a while, it fails.&lt;/p>
&lt;p>Maybe some prompt tweaks will make the test pass more consistently, lowering the temperature too,
or using techniques like few-shot prompting will help the model better understand what it has to do.
But in some circumenstances, you can&amp;rsquo;t find ways around those weird failures,
and the sole solution I found was to make a test &lt;em>retryable&lt;/em>.&lt;/p></description><content:encoded>
<![CDATA[<p>As I work a lot with Large Language Models (LLMs), I often have to deal with flaky test cases,
because LLMs are not always consistent and deterministic in their responses.
Thus, sometimes, a test passes maybe a few times in a row, but then, once in a while, it fails.</p>
<p>Maybe some prompt tweaks will make the test pass more consistently, lowering the temperature too,
or using techniques like few-shot prompting will help the model better understand what it has to do.
But in some circumenstances, you can&rsquo;t find ways around those weird failures,
and the sole solution I found was to make a test <em>retryable</em>.</p>
<p>If a test fails, let&rsquo;s retry a few more times (2 or 3 times) till it passes.
But if it fails everytime in spite of the retries, then it&rsquo;ll just fail as expected.</p>
<p>I wrote JUnit <em>Rules</em> in the past for such situations, but that was in the JUnit 4 days.
Now, I&rsquo;m using JUnit 5, and although it&rsquo;s possible to make JUnit 4 tests run under JUnit 5,
I thought it was a great opportunity to try creating a JUnit 5 <em>extension</em>,
which is the more powerful mechanism that replaces JUnit 4 rules.</p>
<h2 id="it-all-starts-with-a-failing-test-case">It all starts with a failing test case</h2>
<p>Let&rsquo;s say you have an hypothetical flaky test that fails a few times in a row:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>count<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>1;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Test</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">test_custom_junit_retry_extension</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>assertThat(count<span style="color:#666">++</span>).<span style="color:#4070a0">isEqualTo</span>(4);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The first 3 executions will see an assertion failure, but the 4th would succeed
as the counter is then equal to <code>4</code>.</p>
<p>I&rsquo;d like to annotate this test method with a custom annotation that indicates the number of times I&rsquo;m ready to retry that test:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>count<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>1;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Test</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@ExtendWith</span>(RetryExtension.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Retry</span>(4)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">test_custom_junit_retry_extension</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>assertThat(count<span style="color:#666">++</span>).<span style="color:#4070a0">isEqualTo</span>(4);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This <code>@ExtendWith()</code> annotation indicates that I&rsquo;m registering a JUnit 5 extension.
And <code>@Retry(4)</code> is a custom annotation that I&rsquo;ve created.</p>
<p>Note that <code>@ExtendWith()</code> can be at the class-level, but it can also live at the method level.</p>
<p>Let&rsquo;s have a look at the <code>@Retry</code> annotation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.lang.annotation.Retention</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.lang.annotation.RetentionPolicy</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Retention</span>(RetentionPolicy.<span style="color:#4070a0">RUNTIME</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#555;font-weight:bold">@interface</span><span style="color:#bbb"> </span>Retry<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">int</span><span style="color:#bbb"> </span><span style="color:#06287e">value</span>()<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">default</span><span style="color:#bbb"> </span>3;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>By default, I attempt the test 3 times, if no number is provided for the annotation value.</p>
<p>Now it&rsquo;s time to see how the extension code works:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.junit.jupiter.api.extension.ExtensionContext</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.junit.jupiter.api.extension.TestExecutionExceptionHandler</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.concurrent.atomic.AtomicInteger</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">RetryExtension</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>TestExecutionExceptionHandler<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>AtomicInteger<span style="color:#bbb"> </span>counter<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>AtomicInteger(1);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">printError</span>(Throwable<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">err</span>.<span style="color:#4070a0">println</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;Attempt test execution #&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>counter.<span style="color:#4070a0">get</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34; failed (&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>e.<span style="color:#4070a0">getClass</span>().<span style="color:#4070a0">getName</span>()<span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;thrown):  &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>e.<span style="color:#4070a0">getMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">handleTestExecutionException</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ExtensionContext<span style="color:#bbb"> </span>extensionContext,<span style="color:#bbb"> </span>Throwable<span style="color:#bbb"> </span>throwable)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>Throwable<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>printError(throwable);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>extensionContext.<span style="color:#4070a0">getTestMethod</span>().<span style="color:#4070a0">ifPresent</span>(method<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#902000">int</span><span style="color:#bbb"> </span>maxExecutions<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>method.<span style="color:#4070a0">getAnnotation</span>(Retry.<span style="color:#4070a0">class</span>)<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>method.<span style="color:#4070a0">getAnnotation</span>(Retry.<span style="color:#4070a0">class</span>).<span style="color:#4070a0">value</span>()<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>1;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">while</span><span style="color:#bbb"> </span>(counter.<span style="color:#4070a0">incrementAndGet</span>()<span style="color:#bbb"> </span><span style="color:#666">&lt;=</span><span style="color:#bbb"> </span>maxExecutions)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>extensionContext.<span style="color:#4070a0">getExecutableInvoker</span>().<span style="color:#4070a0">invoke</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>method,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>extensionContext.<span style="color:#4070a0">getRequiredTestInstance</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">return</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(Throwable<span style="color:#bbb"> </span>t)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>printError(t);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(counter.<span style="color:#4070a0">get</span>()<span style="color:#bbb"> </span><span style="color:#666">&gt;=</span><span style="color:#bbb"> </span>maxExecutions)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span>t;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s go through the code step by step:</p>
<ul>
<li>The extension has a counter to count the number of executions</li>
<li>a <code>printError()</code> method is used to report the assertion failure or exception</li>
<li>The class implements the <code>TestExecutionExceptionHandler</code> interface</li>
<li>That interface requires the method <code>handleTestExecutionException()</code> to be implemented</li>
<li>This method is invoked when a test throws some exception</li>
<li>If an exception is thrown, let&rsquo;s see if the method is annotated with the <code>@Retry</code> annotation</li>
<li>and let&rsquo;s retrieve the number of attempts demanded by the developer</li>
<li>Then let&rsquo;s loop to do some more executions of the test method, until it passes or up to the number of attempts</li>
</ul>
<h2 id="missing-standard-junit-5-extension">Missing standard JUnit 5 extension?</h2>
<p>I thought a <code>@Retry</code> extension would be pretty common, and that it would be integrated in JUnit 5 directly.
Or at least, some library would provide common JUnit 5 extensions?
But my search didn&rsquo;t yield anything meaningful.
Did I overlook or miss something?</p>
<p>At least now, I have a solution to work around some flaky tests, thanks to this retryable extension!</p>
<h2 id="going-further">Going further</h2>
<p>If you want to learn more about JUnit 5 extensions,
there were a few resources that helped me develop this extension.
First of all, two artciles from Baeldung on
<a href="https://www.baeldung.com/junit-5-migration">Migrating from JUnit 4 to JUnit 5</a>
to understand the changes since JUnit 4, and this
<a href="https://www.baeldung.com/junit-5-extensions">Guide to JUnit 5 Extensions</a>.
And of course, the JUnit 5 documentation on
<a href="https://junit.org/junit5/docs/current/user-guide/#extensions">extensions</a>.</p>
<blockquote>
<h2 id="update">Update</h2>
<p>I&rsquo;m glad I shared this article on Twitter, because I immediately got a response!
Thanks <a href="https://x.com/donal_tweets">@donal_tweets</a>
for your <a href="https://x.com/donal_tweets/status/1830260408462221622">answer</a>!</p>
<p>The <a href="https://junit-pioneer.org/">JUnit Pioneer</a> library provides a JUnit 5 extension pack,
which includes a powerful <a href="https://junit-pioneer.org/docs/retrying-test/">retrying extension</a>.
Replace the usual <code>@Test</code> annotation with <code>@RetryingTest</code>.
You can specify the number of attempts, the minimum number of successes, or some wait time before retries.</p>
<p>There&rsquo;s also a <a href="https://github.com/artsok/rerunner-jupiter">rerunner</a> extension that is quite similar.</p>
<p>My friend <a href="https://x.com/aheritier">@aheritier</a> also suggested that
Maven Surefire can be configured to automatically
<a href="https://maven.apache.org/surefire/maven-surefire-plugin/examples/rerun-failing-tests.html">retry failing tests</a>
a few times, thanks to a special flag:</p>
<pre tabindex="0"><code>mvn -Dsurefire.rerunFailingTestsCount=2 test
</code></pre><p>In my case, I don&rsquo;t want to retry all failing tests, but only a specific one that I know is flaky.</p>
<p>For those who prefer Gradle over Maven, there&rsquo;s a Gradle plugin as well:
<a href="https://github.com/gradle/test-retry-gradle-plugin">test-retry</a>.
You can configure the behavior in your <code>build.gradle</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>test <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>   retry <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>       maxRetries <span style="color:#666">=</span> <span style="color:#40a070">2</span>
</span></span><span style="display:flex;"><span>       maxFailures <span style="color:#666">=</span> <span style="color:#40a070">20</span>
</span></span><span style="display:flex;"><span>       failOnPassedAfterRetry <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>   <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Someone also suggested me to use fuzzy assertions, but my test is very binary as it either fails or succeeds.
There&rsquo;s no threshold, or value that would fit within some bounds.</p></blockquote>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Let LLM suggest Instagram hashtags for your pictures</title><link>https://glaforge.dev/posts/2024/08/12/let-llm-suggest-instagram-hashtags/</link><pubDate>Mon, 12 Aug 2024 21:15:19 +0200</pubDate><guid>https://glaforge.dev/posts/2024/08/12/let-llm-suggest-instagram-hashtags/</guid><description>&lt;p>In this article, we&amp;rsquo;ll explore another great task where Large Language Models shine: &lt;strong>entity and data extraction&lt;/strong>.
LLMs are really useful beyond just mere chatbots (even smart ones using Retrieval Augmented Generation).&lt;/p>
&lt;p>Let me tell you a little story of a handy application we could build, for wannabe Instagram influencers!&lt;/p>
&lt;h2 id="great-instagram-hashtags-thanks-to-llms">Great Instagram hashtags, thanks to LLMs&lt;/h2>
&lt;p>When posting Instagram pictures, I often struggle with finding the right hashtags to engage with the community.
Large Language Models are pretty creative, and they&amp;rsquo;ve certainly seen a bunch of Instagram pictures with their descriptions.&lt;/p></description><content:encoded>
<![CDATA[<p>In this article, we&rsquo;ll explore another great task where Large Language Models shine: <strong>entity and data extraction</strong>.
LLMs are really useful beyond just mere chatbots (even smart ones using Retrieval Augmented Generation).</p>
<p>Let me tell you a little story of a handy application we could build, for wannabe Instagram influencers!</p>
<h2 id="great-instagram-hashtags-thanks-to-llms">Great Instagram hashtags, thanks to LLMs</h2>
<p>When posting Instagram pictures, I often struggle with finding the right hashtags to engage with the community.
Large Language Models are pretty creative, and they&rsquo;ve certainly seen a bunch of Instagram pictures with their descriptions.</p>
<p>So it&rsquo;s natural to try asking an AI like <a href="https://gemini.google.com/app">Gemini</a> what it could suggest in terms of hashtags:</p>
<p><figure>
  <a href="#img-efd146afabd265680009e826b570b172">
    <img src="/img/gemini/gemini-instagram-hashtags.png"
      alt="Gemini Instagram Hashtag suggestion"
       />
  </a>
  <figcaption>Gemini Instagram Hashtag suggestion</figcaption>
</figure>
<div class="lightbox" id="img-efd146afabd265680009e826b570b172">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/gemini-instagram-hashtags.png"
    alt="Gemini Instagram Hashtag suggestion"
     />
  <div class="lightbox-caption">Gemini Instagram Hashtag suggestion</div>
</div>
</p>
<p>This is a picture taken in the port of Heraklion in Crete, a Greek island.
Here&rsquo;s the <a href="https://g.co/gemini/share/476eb5dd974a">conversation</a> I had with Gemini, if you want to see all the tags it suggested.
I think you&rsquo;ll agree with me that those hashtags look pretty good.
Gemini was able to recognise where the picture was taken, as it had tags like <code>#heraklion</code>, <code>#crete</code>, <code>#greece</code>, <code>#greekisland</code>, etc.
In another attempt, it even told me the name of the fortress of the Venetian port, and suggested other tags along those lines.
We also have several tags typically found on Instagram, like <code>#travelgram</code>, <code>#instatravel</code>, and more specific tags like <code>#cretephotography</code>.</p>
<p>My developer mind started quickly spinning with ideas of an online tool to help users be more creative with their instagram tags.
Armed with my usual tools of trade: Java, and <a href="https://docs.langchain4j.dev/">LangChain4j</a>,
I tried to see how I could implement such a tool.</p>
<p>When you want to integrate an LLM into an application, it&rsquo;s important to be able to use more structured outputs than plain text.
And what&rsquo;s great with the Gemini 1.5 Flash model is that it can generate a JSON response,
and Gemini 1.5 Pro can even follow a specific JSON schema
(also called <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output">controlled generation</a>).</p>
<h2 id="lets-implement-an-instagram-hashtag-generator">Let&rsquo;s implement an Instagram hashtag generator</h2>
<p>First, let&rsquo;s see what the Gemini API responds with a plain prompt, without trying to return some JSON payload:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>modelCreative<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>messages<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messages.<span style="color:#4070a0">add</span>(SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You are an Instagram influencer and expert.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You master the fine art of choosing the best creative hashtags
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    to share users&#39; best pictures, and to ensure engagement with
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    the Instagram community is the highest possible.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messages.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ImageContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>Paths.<span style="color:#4070a0">get</span>(<span style="color:#4070a0">&#34;src/main/resources/travel-picture.jpg&#34;</span>).<span style="color:#4070a0">toUri</span>()),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#4070a0">&#34;What are the best Instagram hashtags to describe that picture?&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>modelCreative.<span style="color:#4070a0">generate</span>(messages);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>responseText<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(responseText);<span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>First, we instantiate a Vertex AI Gemini chat model</li>
<li>We prepare a list of messages: one system message to describe the task the LLM must achieve, and one compound message made of the picture and the request to generate hashtags for it. Gemini is a <strong>multimodal</strong> model that accepts text, but also images, audios, videos, and PDF files.</li>
<li>Then we ask Gemini to generate a response, which we then print.</li>
</ul>
<p>The Gemini LLM API will reply with a nice and creative response suggesting lots of great hashtags, along with some interesting explanations:</p>
<pre tabindex="0"><code>Here are some relevant hashtags for your picture:

**General Hashtags:**
#greece #greekislands #crete #creteisland #cretelife #holiday #travel
#vacay #instatravel #travelgram #beautifuldestinations
#travelphotography #photooftheday #instagood #igdaily #instapic

**Specific Hashtags:**
#heraklion #heraklioncrete #venetianharbour #harbourlife #boatlife
#boatday #greekboats #greekharbour #beautifulcrete #creteharbour
#cretevibes #greece_gram #crete_gram #greece_travel

**Trending Hashtags:**
#wanderlust #traveltuesday #instagood #travelgram
#beautifuldestinations #adventuretime

**Hashtags for Engagement:**
#explorecrete #cretelove #lovegreece #greecevibes #cretephoto
#creteexperience #discovercrete

Make sure to also use a mix of hashtags with different lengths and
popularity. You can also use location hashtags to target people in
your area.

For example, you could use hashtags like #heraklioncrete or
#creteharbour to target people who are interested in visiting that
specific location.

You should also consider using a mix of popular and less popular
hashtags. Popular hashtags will help your post reach a wider audience,
while less popular hashtags can help your post stand out from the
crowd.

**Tips for Choosing Hashtags:**

* Research relevant hashtags. Look at what other users are using and
see what&#39;s trending.
* Use a mix of general and specific hashtags.
* Don&#39;t use too many hashtags.
* Keep your hashtags relevant to your image.

By using a mix of relevant and engaging hashtags, you can increase
your chances of getting your post seen by a wider audience and improve
your engagement on Instagram.
</code></pre><p>Now, let&rsquo;s tweak this code to output the Instagram hashtags as a JSON array of hashtag strings.
With LangChain4j&rsquo;s Gemini integration, it&rsquo;s pretty trivial, we can specify that we want to return JSON payloads,
thanks to the <code>responseMimeType()</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>modelCreative<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s update the system instruction to be explicit about what the JSON should look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>messages<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messages.<span style="color:#4070a0">add</span>(SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You are an Instagram influencer and expert.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You master the fine art of choosing the best creative hashtags
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    to share users&#39; best pictures, and to ensure engagement with
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    the Instagram community is the highest possible.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Return a JSON array containing the hashtags as strings, for example:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    ```json
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    [&#34;#beach&#34;, &#34;#island&#34;, &#34;#traveltahiti&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    ```
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now let&rsquo;s see the LLM&rsquo;s response:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greece&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#crete&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#heraklion&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#cretelife&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#mediterraneansea&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#creteisland&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greecevacations&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greekislands&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#cretetravel&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#heraklionport&#34;</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>On one hand, it&rsquo;s nice that Gemini obeyed us and generated the request JSON array of hashtags.
However, notice that there are fewer hashtags, which are also a bit less creative.</p>
<h2 id="llms-are-less-creative-when-constrained">LLMs are less creative when constrained</h2>
<p>It&rsquo;s not the first time I noticed that behavior with an LLM.
I tried this with other LLMs like ChatGPT.
It seems that LLMs are less creative when they are constrained to follow a stricter output.</p>
<p>And indeed, what actually trigged this article idea and example was this paper that I came across:
<a href="https://arxiv.org/abs/2408.02442#">Let Me Speak Freely? A Study on the Impact of
Format Restrictions on Performance of Large Language Models</a>
which has been published on arXiv a few days ago,
which confirms my intuition that LLMs are less creative when using controlled generation:</p>
<blockquote>
<p>Structured generation, the process of producing content in standardized formats like JSON and XML,
is widely utilized in real-world applications to extract key output information from large language models (LLMs).
This study investigates whether such constraints on generation space impact LLMs&rsquo; abilities,
including reasoning and domain knowledge comprehension.
Specifically, we evaluate LLMs&rsquo; performance when restricted to adhere to structured formats
versus generating free-form responses across various common tasks.
Surprisingly, <strong>we observe a significant decline in LLMs&rsquo; reasoning abilities under format restrictions</strong>.
Furthermore, we find that stricter format constraints generally lead to greater performance degradation in reasoning tasks.</p></blockquote>
<h2 id="a-better-solution-with-a-two-step-approach-with-entity-extraction">A better solution with a two-step approach with entity extraction</h2>
<p>Since LLMs are not as good when we control their generation, we can try a slighly smarter approach:</p>
<ul>
<li>Firstly, we can ask the LLM to give its usual plain-text creative answer,</li>
<li>Secondly, we ask the LLM to actually extract all the hashtags from the previous response, using controlled gneration.</li>
</ul>
<p>LLMs are great at various classical Natural Language Processing tasks like <strong>entity extraction</strong>.
And here, indeed, what we want is to just extract the hashtags from the plain-text response.</p>
<p>For such a task, controlled generation won&rsquo;t hinder the creativity, and will be acurate and extract correctly all the tags.
The aforementioned paper seemed to also hint at the fact that controlled generation can actually help with some tasks like classification.</p>
<p>Let&rsquo;s have a look at our improved approach.
We keep the first attempt from the beginning of this article, without using controlled generation,
but we&rsquo;ll use a different configuration for the second step:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>modelExtraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(SchemaHelper.<span style="color:#4070a0">fromClass</span>(String<span style="color:#666">[]</span>.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>messagesForExtraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ArrayList<span style="color:#666">&lt;&gt;</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messagesForExtraction.<span style="color:#4070a0">add</span>(SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Your job is to extract Instagram hashtags from a given text, and
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    return them as a JSON array of strings representing those hashtags.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>messagesForExtraction.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Here is the text to extract Instagram hashtags from:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>responseText));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>responseFromExtraction<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>modelExtraction.<span style="color:#4070a0">generate</span>(messagesForExtraction);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>extractedTagsJson<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>responseFromExtraction.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(extractedTagsJson);<span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>In this example, I used Gemini 1.5 Pro instead of Gemini 1.5 Flash to show you the use of the
<code>responseSchema()</code> method which allows you to specify the exact shape of the JSON we want to retrieve.
I could have used Gemini 1.5 Flash like before, but I have to give a bit more prompting help to specify the JSON schema.</li>
<li>This time, we use a different system message to explain the task of hashtag extraction.</li>
<li>And the user message reuses the creative response from the previous LLM call to extract hashtags from it.</li>
</ul>
<p>So what&rsquo;s the output like?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greece&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greekislands&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#crete&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#creteisland&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#cretelife&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#holiday&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#travel&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#vacay&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#instatravel&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#travelgram&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#beautifuldestinations&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#travelphotography&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#photooftheday&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#instagood&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#igdaily&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#instapic&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#heraklion&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#heraklioncrete&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#venetianharbour&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#harbourlife&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#boatlife&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#boatday&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greekboats&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greekharbour&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#beautifulcrete&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#creteharbour&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#cretevibes&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greece_gram&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#crete_gram&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greece_travel&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#wanderlust&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#traveltuesday&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#instagood&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#travelgram&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#beautifuldestinations&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#adventuretime&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#explorecrete&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#cretelove&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#lovegreece&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#greecevibes&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#cretephoto&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#creteexperience&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;#discovercrete&#34;</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>Excellent! It managed to extract all the tags of the creative response!</p>
<h2 id="conclusion--discussion">Conclusion &amp; discussion</h2>
<p>Even if researchers found that LLMs may be less creative when constrained with controlled generation,
we can find workarounds to prevent suffering from this limitation, like we did with this two-step approach
by making two calls.
The first call is a creative one, while the second is the data extraction one.</p>
<p>One drawback of this approach, however, is that we had to make two calls to the LLM.
So this can be a bit more costly in terms of tokens generated.
And it also adds latency, because we have two calls instead of just one.
So you might have to balance cost &amp; lantency with quality, depending on your use case.</p>
<p>But it&rsquo;s always great to have the choice!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Gemini Nano running locally in your browser</title><link>https://glaforge.dev/posts/2024/08/07/gemini-nano-running-locally-in-your-browser/</link><pubDate>Wed, 07 Aug 2024 15:57:33 +0200</pubDate><guid>https://glaforge.dev/posts/2024/08/07/gemini-nano-running-locally-in-your-browser/</guid><description>&lt;p>Generative AI use cases are usually about running large language models somewhere in the cloud.
However, with the advent of smaller models and open models, you can run them locally on your machine,
with projects like &lt;a href="https://github.com/ggerganov/llama.cpp">llama.cpp&lt;/a> or &lt;a href="https://ollama.com/">Ollama&lt;/a>.&lt;/p>
&lt;p>And what about in the browser?
With &lt;a href="https://github.com/google-ai-edge/mediapipe">MediaPipe&lt;/a> and &lt;a href="https://www.tensorflow.org/js">TensorFlow.js&lt;/a>,
you can train and run small neural networks for tons of fun and useful tasks
(like recognising hand movements through the webcam of your computer), and it&amp;rsquo;s also possible to run
&lt;a href="https://ai.google.dev/gemma/">Gemma&lt;/a> 2B and even 7B models.&lt;/p></description><content:encoded>
<![CDATA[<p>Generative AI use cases are usually about running large language models somewhere in the cloud.
However, with the advent of smaller models and open models, you can run them locally on your machine,
with projects like <a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a> or <a href="https://ollama.com/">Ollama</a>.</p>
<p>And what about in the browser?
With <a href="https://github.com/google-ai-edge/mediapipe">MediaPipe</a> and <a href="https://www.tensorflow.org/js">TensorFlow.js</a>,
you can train and run small neural networks for tons of fun and useful tasks
(like recognising hand movements through the webcam of your computer), and it&rsquo;s also possible to run
<a href="https://ai.google.dev/gemma/">Gemma</a> 2B and even 7B models.</p>
<p>But there&rsquo;s something interesting cooking these days: <strong>built-in language models in the browser</strong>!</p>
<p>The Chrome developers are working on a new Web API to integrate LLMs in the browser,
and are experimenting with the <a href="https://deepmind.google/technologies/gemini/nano/">Gemini Nano</a> model
(already integrated in some smartphones like Samsung Galaxy or Google Pixel phones)
inside <a href="https://www.google.com/chrome/canary/">Chrome Canary</a>.</p>
<h2 id="getting-started-with-gemini-nano-and-chrome-canary">Getting started with Gemini Nano and Chrome Canary</h2>
<p>I&rsquo;m sure you want to experiment with that too? Let&rsquo;s see how to proceed:</p>
<ul>
<li>
<p>First of all, you&rsquo;ll need to download <a href="https://www.google.com/chrome/canary/">Chrome Canary</a></p>
</li>
<li>
<p>In <code>chrome://flags</code>, you must <strong>enable</strong> two experiments:</p>
<ul>
<li><code>Prompt API for Gemini Nano</code> and</li>
<li><code>Enables optimization guide on device</code>.</li>
</ul>
</li>
<li>
<p>You&rsquo;ll have to restart the browser, after having enabled those two flags.</p>
</li>
</ul>
<p>It may take quite a bit of time to download Gemini Nano (as it&rsquo;s a small model, it takes only around 1.7GB of space, but you&rsquo;ll need about 20GB at installation time on your hard drive)
but the API will tell you if the model weights are not fully downloaded yet.</p>
<h2 id="experimenting-in-the-playground">Experimenting in the playground</h2>
<p>Now it&rsquo;s time to play!
Let&rsquo;s see what this embedded Gemini Nano can do, in the <a href="https://chrome.dev/prompt-api-playground/">Prompt API playground</a>.
This is a simple form where you can send prompts to the model, and see its replies.</p>
<p><figure>
  <a href="#img-9cfc8d49ee281ab5fc1dabefa1e91e8e">
    <img src="/img/gemini/nano-playground.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9cfc8d49ee281ab5fc1dabefa1e91e8e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/nano-playground.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Looks like it&rsquo;s smart enough to know that no cat ever went on the moon!</p>
<h2 id="a-bit-of-code">A bit of code</h2>
<p>The <a href="https://github.com/tomayac/prompt-api-playground">code of this demo</a> is available on Github.</p>
<p>Let&rsquo;s have a look at the key lines of the <strong>Prompt API</strong> usage.</p>
<p>To know if the browser supports the Prompt API, you&rsquo;ll need to check the existence of the new <code>ai</code> object on <code>window</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">if</span> (<span style="color:#666">!</span><span style="color:#007020">window</span>.ai) {
</span></span><span style="display:flex;"><span>    ...
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then you&rsquo;ll have to create a <strong>text session</strong> with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> session <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> <span style="color:#007020">window</span>.ai.createTextSession();
</span></span></code></pre></div><p>Then you can either wait for the full response, or stream the tokens as they are generated.
Here, let&rsquo;s see the streaming scenario, and how to iterate over the streamed tokens:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> stream <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> session.promptStreaming(
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;What&#39;s the name of the first cat who stepped on the moon?&#34;</span>
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span> <span style="color:#007020;font-weight:bold">await</span> (<span style="color:#007020;font-weight:bold">const</span> chunk <span style="color:#007020;font-weight:bold">of</span> stream) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">var</span> fullResponse <span style="color:#666">=</span> chunk.trim();
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// do something with the response, like appending it to a DOM node
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>}
</span></span></code></pre></div><p>If you&rsquo;re not streaming the response, you can also do as follows,
to get the response in one go once it&rsquo;s fully generated:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> result <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> session.prompt(
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;What&#39;s the name of the first cat who stepped on the moon?&#34;</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><h2 id="but-why-running-ai-in-the-browser">But why running AI in the browser?</h2>
<p>Maybe I should have started there, afterall?
Why would you want to run models locally in the browser, rather than using a cloud-hosted one?</p>
<p>As the <a href="https://developer.chrome.com/docs/ai/built-in">documentation</a> outlines:</p>
<ul>
<li>For <strong>privacy reasons</strong>: you may want to do local processing of sensitive data, to avoid sending such information on the web.</li>
<li>For <strong>latency gains</strong>: once the model is loaded in the browser (in about 3 seconds on my machine), the model responds super fast to all subsequent requests.
So you can have a very snappy experience, without the long roundtrip through the internet!</li>
<li>For <strong>lower costs</strong>: since all the AI inference is done in the browser, it&rsquo;s not going to cost you anything on the server-side.</li>
<li>For <strong>offline usage</strong>: as it runs in the browser, even if you lost your internet connection, your Web UI will continue to function with all its smart AI features.</li>
</ul>
<h2 id="interesting-resources">Interesting resources</h2>
<ul>
<li>Checkout the <a href="https://chrome.dev/prompt-api-playground/">Prompt API playground</a> to play with it (after having followed the instructions above).</li>
<li>Have a look at the <a href="https://github.com/tomayac/prompt-api-playground">sources</a> of the playground to learn how the demo is done.</li>
<li>There&rsquo;s a nice <a href="https://medium.com/google-cloud/google-chrome-has-a-secret-ai-assistant-9accb95f1911">publication</a> that shows how to use the Prompt API to summarize the content of the web page displayed in your browser.</li>
<li>The HuggingFace people have an <a href="https://huggingface.co/blog/Xenova/run-gemini-nano-in-your-browser">extended article</a> on how to run Gemini Nano in the browser, with some advanced details about the Prompt API.</li>
<li>Read the pages that explains the <a href="https://developer.chrome.com/docs/ai/built-in">goals of the built-in AI</a>.</li>
<li>It&rsquo;s interesting to glance through the <a href="https://github.com/explainers-by-googlers/prompt-api/">explainer</a> of the Prompt API to understand how it&rsquo;s been designed.</li>
<li>And the best resource for the end, the <a href="https://docs.google.com/document/d/1VG8HIyz361zGduWgNG7R_R8Xkv0OOJ8b5C9QKeCjU0c/edit">user guide of the built-in AI early preview</a>, which gives lots of details about the Prompt API.</li>
</ul>
<h2 id="summary">Summary</h2>
<p>I&rsquo;ve been focusing mostly on large language models in the cloud so far,
in particular <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstarts/quickstart-multimodal#gemini-text-only-samples-java">Gemini</a>,
but I&rsquo;m excited at the prospect of the interesting use cases that it can enable.</p>
<p>Imagine, for example, a travel itinerary application, that would store all the information of your trip locally (in IndexedDB or a WebAssembly-fied sqlite),
and you could ask offline all the questions you want about the journey? (basically, <strong>RAG in the browser</strong>!)
No need to hunt for a public wifi network or a local SIM card.</p>
<p>There are also many tasks some browser extension could handle:</p>
<ul>
<li>When preparing my podcast episode and show notes, I could ask Gemini Nano to make a 5-bullet-point summary of the article I&rsquo;m reading.</li>
<li>When reading the reviews for a product, I could get a sentiment analysis signal that tells me if customers are happy with that product.</li>
</ul>
<p>We could also think of some hybrid scenarios, as both cloud-hosted and local-running models could complement each other.</p>
<p>I hope this Web API will become a standard and that other browsers support it too, and offer different models as well.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Sentiment analysis with few-shot prompting</title><link>https://glaforge.dev/posts/2024/07/30/sentiment-analysis-with-few-shots-prompting/</link><pubDate>Tue, 30 Jul 2024 13:06:16 +0200</pubDate><guid>https://glaforge.dev/posts/2024/07/30/sentiment-analysis-with-few-shots-prompting/</guid><description>&lt;p>In a rencent article, we talked about
&lt;a href="https://glaforge.dev/posts/2024/07/11/text-classification-with-gemini-and-langchain4j/">text classification&lt;/a>
using &lt;a href="https://deepmind.google/technologies/gemini/">Gemini&lt;/a> and &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>.&lt;/p>
&lt;p>A typical example of text classification is the case of &lt;strong>sentiment analysis&lt;/strong>.&lt;/p>
&lt;p>In my LangChain4j-powered Gemini &lt;a href="https://github.com/glaforge/gemini-workshop-for-java-developers/">workshop&lt;/a>,
I used this use case to illustrate the classification problem:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>ChatLanguageModel&lt;span style="color:#bbb"> &lt;/span>model&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>VertexAiGeminiChatModel.&lt;span style="color:#4070a0">builder&lt;/span>()&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>.&lt;span style="color:#4070a0">project&lt;/span>(System.&lt;span style="color:#4070a0">getenv&lt;/span>(&lt;span style="color:#4070a0">&amp;#34;PROJECT_ID&amp;#34;&lt;/span>))&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>.&lt;span style="color:#4070a0">location&lt;/span>(System.&lt;span style="color:#4070a0">getenv&lt;/span>(&lt;span style="color:#4070a0">&amp;#34;LOCATION&amp;#34;&lt;/span>))&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>.&lt;span style="color:#4070a0">modelName&lt;/span>(&lt;span style="color:#4070a0">&amp;#34;gemini-1.5-flash-001&amp;#34;&lt;/span>)&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>.&lt;span style="color:#4070a0">maxOutputTokens&lt;/span>(10)&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>.&lt;span style="color:#4070a0">maxRetries&lt;/span>(3)&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>.&lt;span style="color:#4070a0">build&lt;/span>();&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>PromptTemplate&lt;span style="color:#bbb"> &lt;/span>promptTemplate&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>PromptTemplate.&lt;span style="color:#4070a0">from&lt;/span>(&lt;span style="color:#4070a0">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> Analyze the sentiment of the text below.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> Respond only with one word to describe the sentiment.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> INPUT: This is fantastic news!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> OUTPUT: POSITIVE
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> INPUT: Pi is roughly equal to 3.14
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> OUTPUT: NEUTRAL
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> INPUT: I really disliked the pizza. Who would use pineapples as a pizza topping?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> OUTPUT: NEGATIVE
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> INPUT: {{text}}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> OUTPUT:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>);&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>Prompt&lt;span style="color:#bbb"> &lt;/span>prompt&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>promptTemplate.&lt;span style="color:#4070a0">apply&lt;/span>(&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>Map.&lt;span style="color:#4070a0">of&lt;/span>(&lt;span style="color:#4070a0">&amp;#34;text&amp;#34;&lt;/span>,&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#4070a0">&amp;#34;I love strawberries!&amp;#34;&lt;/span>));&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>Response&lt;span style="color:#666">&amp;lt;&lt;/span>AiMessage&lt;span style="color:#666">&amp;gt;&lt;/span>&lt;span style="color:#bbb"> &lt;/span>response&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>model.&lt;span style="color:#4070a0">generate&lt;/span>(prompt.&lt;span style="color:#4070a0">toUserMessage&lt;/span>());&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>System.&lt;span style="color:#4070a0">out&lt;/span>.&lt;span style="color:#4070a0">println&lt;/span>(response.&lt;span style="color:#4070a0">content&lt;/span>().&lt;span style="color:#4070a0">text&lt;/span>());&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I used a &lt;code>PromptTemplate&lt;/code> to craft the prompt, with a &lt;code>{{text}}&lt;/code> placeholder value to analyze the sentiment of that particular text.&lt;/p></description><content:encoded>
<![CDATA[<p>In a rencent article, we talked about
<a href="https://glaforge.dev/posts/2024/07/11/text-classification-with-gemini-and-langchain4j/">text classification</a>
using <a href="https://deepmind.google/technologies/gemini/">Gemini</a> and <a href="https://docs.langchain4j.dev/">LangChain4j</a>.</p>
<p>A typical example of text classification is the case of <strong>sentiment analysis</strong>.</p>
<p>In my LangChain4j-powered Gemini <a href="https://github.com/glaforge/gemini-workshop-for-java-developers/">workshop</a>,
I used this use case to illustrate the classification problem:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxOutputTokens</span>(10)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxRetries</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>PromptTemplate<span style="color:#bbb"> </span>promptTemplate<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>PromptTemplate.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Analyze the sentiment of the text below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Respond only with one word to describe the sentiment.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: This is fantastic news!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT: POSITIVE
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: Pi is roughly equal to 3.14
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT: NEUTRAL
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: I really disliked the pizza. Who would use pineapples as a pizza topping?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT: NEGATIVE
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: {{text}}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Prompt<span style="color:#bbb"> </span>prompt<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>promptTemplate.<span style="color:#4070a0">apply</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;text&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;I love strawberries!&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(prompt.<span style="color:#4070a0">toUserMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>I used a <code>PromptTemplate</code> to craft the prompt, with a <code>{{text}}</code> placeholder value to analyze the sentiment of that particular text.</p>
<p>Notice that I used the <a href="https://learnprompting.org/docs/basics/few_shot">few-shot prompting</a> technique, with example inputs and outputs.</p>
<h2 id="few-shot-prompting-with-a-list-of-messages">Few-shot prompting with a list of messages</h2>
<p>Somehow, I had the impression that this <code>INPUT/OUTPUT</code> notation was a bit of a <em>hack</em>
to encourage the LLM to believe this is an actual exchange between the user and the AI.</p>
<p>I believed it would be cleaner to use a real list of messages that alternate user and AI messages.
So I implemented this alternative approach, but haven&rsquo;t yet committed it to my workshop repository.</p>
<p>Meanwhile, as I was chatting with my colleague <a href="https://x.com/ddobrin">Dan Dobrin</a>,
he pointed me at this very recent blog <a href="https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/">post</a>
from the LangChain people, who were investigating <em>few-shot prompting to improve tool-calling performance</em>.</p>
<p>What&rsquo;s interesting in their analysis was that overall, on this anecdata example,
it seems <strong>LLMs do better with real user/AI messages than with a big string of inputs/outputs</strong>.</p>
<p>Let&rsquo;s see how to implement the same approach, with a real exchange of messages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>fewShotPrompts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>List.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Analyze the sentiment of the text below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Respond only with one word to describe the sentiment.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;This is fantastic news!&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;POSITIVE&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;Pi is roughly equal to 3.14&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;NEUTRAL&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;I really disliked the pizza. &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                     </span><span style="color:#4070a0">&#34;Who would use pineapples as a pizza topping?&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;NEGATIVE&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;I love strawberries!&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(fewShotPrompts);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is not much more verbose than the previous approach, as it&rsquo;s still very readable.
And when pulling the few-shot data from an external database, it feels cleaner than concatenating a big string.</p>
<h2 id="more-type-safe-few-shot-prompting-with-messages-and-aiservices">More type-safe few-shot prompting with messages and AiServices</h2>
<p>To further improve on the list of messages tactic, we can use LangChain4j&rsquo;s <code>AiServices</code> concept,
which is a higher-level abstraction than using the model and prompt templates directly.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">enum</span><span style="color:#bbb"> </span>Sentiment<span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>POSITIVE,<span style="color:#bbb"> </span>NEUTRAL,<span style="color:#bbb"> </span>NEGATIVE<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">SentimentAnalysis</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@SystemMessage</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Analyze the sentiment of the text below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Respond only with one word to describe the sentiment.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Sentiment<span style="color:#bbb"> </span><span style="color:#06287e">analyze</span>(String<span style="color:#bbb"> </span>text);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>MessageWindowChatMemory<span style="color:#bbb"> </span>memory<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>MessageWindowChatMemory.<span style="color:#4070a0">withMaxMessages</span>(10);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>memory.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;This is fantastic news!&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>memory.<span style="color:#4070a0">add</span>(AiMessage.<span style="color:#4070a0">from</span>(Sentiment.<span style="color:#4070a0">POSITIVE</span>.<span style="color:#4070a0">name</span>()));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>memory.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;Pi is roughly equal to 3.14&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>memory.<span style="color:#4070a0">add</span>(AiMessage.<span style="color:#4070a0">from</span>(Sentiment.<span style="color:#4070a0">NEUTRAL</span>.<span style="color:#4070a0">name</span>()));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>memory.<span style="color:#4070a0">add</span>(UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;I really disliked the pizza. &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Who would use pineapples as a pizza topping?&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>memory.<span style="color:#4070a0">add</span>(AiMessage.<span style="color:#4070a0">from</span>(Sentiment.<span style="color:#4070a0">NEGATIVE</span>.<span style="color:#4070a0">name</span>()));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>SentimentAnalysis<span style="color:#bbb"> </span>analyzer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiServices.<span style="color:#4070a0">builder</span>(SentimentAnalysis.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatLanguageModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">chatMemory</span>(memory)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(analyzer.<span style="color:#4070a0">analyze</span>(<span style="color:#4070a0">&#34;I love strawberries!&#34;</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>This third and final approach may be a bit more verbose, and introduces a few more LangChain4j concepts
like system messages, chat memory, and the AI service itself, but it has the advantages of being:</p>
<ul>
<li><strong>more type-safe</strong>, as we&rsquo;re using a <code>Sentiment</code> enum, which is easier to manipulate from code,</li>
<li>cleaner, because we&rsquo;re <strong>using system instructions</strong> to instruct the model about what its job is.</li>
</ul>
<p>We created:</p>
<ul>
<li>a Java <code>enum</code> to represent the possible values of the sentiment,</li>
<li>a <code>SentimentAnalysis</code> interface with a clear signature: a text in input, a <code>Sentiment</code> enum value in output,</li>
<li>a <code>@SystemMessage</code> instruction to describe the analysis task,</li>
<li>a <code>ChatMemory</code> (here a <code>MessageWindowChatMemory</code>) to hold the few-shot examples.</li>
</ul>
<p>Then we bind everything together, thanks to <code>AiServices</code>:
the analysis interface that LangChain4j will implement for us, the language model, and the chat memory.</p>
<p>Finally, users just have to call the <code>analyze()</code> method, passing the text to analyze.</p>
<p>I also like the fact that we are <strong>coding against an interface</strong>, and potentially later on,
developers <strong>could swap the implementation</strong> of the sentiment analyzer, and use a different approach.</p>
<h2 id="conclusion">Conclusion</h2>
<p>All three approaches are valid: a big string, a low-level list of messages, or an <code>AiServices</code> abstraction.
But I have a slight preference for the approach that is more type-safe and less <em>stringy</em>.</p>
<p>Just like LangChain4j provides a <code>TextClassification</code> class that leverages vector embeddings for text similarity,
we could investigate whether it would make sense to also add a few-shot prompting classificaction solution directly in the LangChain4j project.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Analyzing video, audio and PDF files with Gemini and LangChain4j</title><link>https://glaforge.dev/posts/2024/07/25/analyzing-videos-audios-and-pdfs-with-gemini-in-langchain4j/</link><pubDate>Thu, 25 Jul 2024 20:08:52 +0200</pubDate><guid>https://glaforge.dev/posts/2024/07/25/analyzing-videos-audios-and-pdfs-with-gemini-in-langchain4j/</guid><description>&lt;p>Certain models like Gemini are &lt;strong>multimodal&lt;/strong>.
This means that they accept more than just text as input.
Some models support text and images, but &lt;strong>Gemini goes further and also supports audio, video, and PDF files&lt;/strong>.
So you can mix and match text prompts and different multimedia files or PDF documents.&lt;/p>
&lt;p>Until LangChain4j 0.32, the models could only support text and images,
but since my &lt;a href="https://github.com/langchain4j/langchain4j/pull/1464">PR&lt;/a> got merged into the newly released
&lt;a href="https://github.com/langchain4j/langchain4j/releases/tag/0.33.0">0.33&lt;/a> version,
you can use all those files with the LangChain4j Gemini module!&lt;/p></description><content:encoded>
<![CDATA[<p>Certain models like Gemini are <strong>multimodal</strong>.
This means that they accept more than just text as input.
Some models support text and images, but <strong>Gemini goes further and also supports audio, video, and PDF files</strong>.
So you can mix and match text prompts and different multimedia files or PDF documents.</p>
<p>Until LangChain4j 0.32, the models could only support text and images,
but since my <a href="https://github.com/langchain4j/langchain4j/pull/1464">PR</a> got merged into the newly released
<a href="https://github.com/langchain4j/langchain4j/releases/tag/0.33.0">0.33</a> version,
you can use all those files with the LangChain4j Gemini module!</p>
<p>Let&rsquo;s have a look!</p>
<h2 id="getting-the-transcription-of-a-podcast-recording">Getting the transcription of a podcast recording</h2>
<p>Are you an avid podcast listener and want to read its transcription?
Or you want to publish that transcription as show-notes of your own podcast on your website?</p>
<p>You can ask Gemini for the transcription with the following code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>AudioContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;https://storage.googleapis.com/cloud-samples-data/&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;generative-ai/audio/pixel.mp3&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;Write a transcription of this audio file&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Above, we created an audio content object with the <code>AudioContent.from(...)</code> method.
This method can take a string which can be a direct URL to a file on the web,
it can be a Google Cloud Storage URL as well (like <code>gs://bucket/audio.mp3</code>).
It is possible to load a local file from your file system with <code>AudioContent.from(Paths.get(&quot;audio.mp3&quot;).toUri())</code>.
You can even pass the base 64 encoded content of the audio file and specify its mime type.</p>
<h3 id="what-else-could-you-do-with-audio-files">What else could you do with audio files?</h3>
<ul>
<li>
<p>If you&rsquo;re in a hurry and don&rsquo;t have time to listen to this one-hour episode,
instead of asking for the whole transcript, you could change the prompt to ask for a summary.
That way you know if it&rsquo;s worth spending an hour to listen to it all.</p>
</li>
<li>
<p>Gemini also accepts several audio files in input, so if you are recording interviews of persons on a specific topic,
you could ask Gemini to contrast the differences in those responses.</p>
</li>
</ul>
<h2 id="preparing-youtube-video-chaptering">Preparing YouTube video chaptering</h2>
<p>Let&rsquo;s say you&rsquo;re a YouTuber, and you want to do your own video chaptering, instead of relying on the the automatic chapters.
How can you do that?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>VideoContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;https://storage.googleapis.com/cloud-samples-data/&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;generative-ai/video/behind_the_scenes_pixel.mp4&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;Prepare chapters for this video file, &#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;using the YouTube chapter notation&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>For this video, the chapters generated look as follows:</p>
<pre tabindex="0"><code>00:00 Making a Film with a Blind Director
00:16 Adam Morse, Filmmaker
00:28 The Film Shoot
00:48 A Blind Man &amp; His Girlfriend
01:15 Google Pixel Phone
01:33 Guided Frame
02:06 The Technical Crew
02:32 Visual Effects
02:45 Misconceptions About Blindness
03:20 Filmmaking with a Team
03:46 Google Accessibility
04:00 One Person&#39;s Perspective
04:29 Adam&#39;s Vision
05:03 A Beautiful Position
05:19 Google Logo
</code></pre><h3 id="what-else-could-you-do-with-videos">What else could you do with videos?</h3>
<ul>
<li>
<p>If a video of your meeting or your conference presentation has been recorded,
you could use this approach to ask Gemini for a summary of the video, to get the various sections, to write the transcript.</p>
</li>
<li>
<p>We often record videos of our family, our children, etc.
It&rsquo;s not always easy to <em>search</em> through those videos.
You could ask Gemini to provide a summary of the video,
that you would then index with some search engine, or just do some simple <em>grep</em> search from the command-line.</p>
</li>
</ul>
<h2 id="asking-questions-about-pdf-documents">Asking questions about PDF documents</h2>
<p>Let&rsquo;s have a look at one last example: PDF documents.</p>
<p>With LangChain4j, it&rsquo;s possible to use the Apache Tika-based document loader to get the text content of a PDF.
However, you loose some important semantic information, as the layout may be important,
or the figures may convey as well some critical details.</p>
<p>Fortunately, Gemini can ingest PDF documents directly, without an intermediate text transcription.</p>
<p>This allows you to ask questions about PDf documents, and since Gemini has a very large context window,
it&rsquo;s able to analyze very big documents, or several documents at the same time,
without having to implement your own RAG system (Retrieval Augmented Generation).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>PdfFileContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;https://proceedings.neurips.cc/paper_files/paper/2017&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#666">+</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>TextContent.<span style="color:#4070a0">from</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;Give a summary of this paper&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>This example analyzes the famous <em>&ldquo;Attention is all you need&rdquo;</em> paper that introduced the concept of <em>Transformer</em> neural networks:</p>
<pre tabindex="0"><code>This paper proposes a novel neural network architecture called the
Transformer, which relies entirely on an attention mechanism and
dispenses with recurrence and convolutions. The Transformer
outperforms existing models on two machine translation tasks, WMT
2014 English-to-German and WMT 2014 English-to-French, while
requiring significantly less training time. The authors argue that
the Transformer&#39;s ability to learn global dependencies without
regard to their distance in the input or output sequences, as well
as its parallelizable nature, make it a promising approach for
sequence modeling and transduction problems. They also present an
analysis of the Transformer&#39;s different components and their effect
on performance. The paper concludes by discussing potential future
directions for research.
</code></pre><h3 id="what-else-could-you-do-with-pdf-documents">What else could you do with PDF documents?</h3>
<ul>
<li>
<p>You can implement some smart question answering solutions over your documents.</p>
</li>
<li>
<p>Gemini can help make sense of differences between two versions of your PDF paper.</p>
</li>
<li>
<p>Gemini allows you to ingest multiple files at the same time,
so it is possible to pass the PDF of your dishwasher manual,
at the same time as a tutorial showing how to repair it,
and then ask the LLM to answer a question on how to fix it.</p>
</li>
</ul>
<h2 id="summary">Summary</h2>
<p>Multimodality is a powerful feature of Gemini,
and now LangChain4j is equiped with the ability to send text, images, audio files, videos, and PDF documents,
potentially all at the same time, to create some innovative multimedia integrations.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Text classification with Gemini and LangChain4j</title><link>https://glaforge.dev/posts/2024/07/11/text-classification-with-gemini-and-langchain4j/</link><pubDate>Thu, 11 Jul 2024 22:26:36 +0200</pubDate><guid>https://glaforge.dev/posts/2024/07/11/text-classification-with-gemini-and-langchain4j/</guid><description>&lt;p>Generative AI has potential applications far beyond chatbots and Retrieval Augmented Generation.
For example, a nice use case is: &lt;strong>text classification&lt;/strong>.&lt;/p>
&lt;p>I had the chance of meeting some customers and prospects who had the need for triaging incoming requests, or for labeling existing data.
In the first case, a government entity was tasked with routing citizen requests to access undisclosed information to the right governmental service that could grant or reject that access. In the second case, a company needed to sort out tons of existing internal documents that were not properly organized, and they wanted to quickly start better structuring this trove of information, by labelling each of these docs into different categories.&lt;/p></description><content:encoded>
<![CDATA[<p>Generative AI has potential applications far beyond chatbots and Retrieval Augmented Generation.
For example, a nice use case is: <strong>text classification</strong>.</p>
<p>I had the chance of meeting some customers and prospects who had the need for triaging incoming requests, or for labeling existing data.
In the first case, a government entity was tasked with routing citizen requests to access undisclosed information to the right governmental service that could grant or reject that access. In the second case, a company needed to sort out tons of existing internal documents that were not properly organized, and they wanted to quickly start better structuring this trove of information, by labelling each of these docs into different categories.</p>
<p>In both situations, the task was a <strong>text classification</strong> one: to put each request or document in a distinct pile, so they could more easily be sorted out, organized, and treated more rapidly.</p>
<p>Before generative AI, text classification would be handled by data scientists who would craft and train dedicated machine learning models for that purpose. But it is now also possible to do the same with the help of large language models.
That&rsquo;s what I&rsquo;d like to explore with you in this article today.</p>
<p>As usual, I&rsquo;ll be using the <a href="https://deepmind.google/technologies/gemini/">Gemini model</a>,
and the <a href="https://docs.langchain4j.dev/">LangChain4j framework</a> for implementing illustrative examples in Java.</p>
<h2 id="text-classification-putting-a-label-on-a-document">Text classification: putting a label on a document</h2>
<p>Before diving into the code, let&rsquo;s step back a short moment to clarify what text classification is about.
When we classify documents, we put a label on them.</p>
<p>For example, in a bug tracker, we could automate adding labels on new tickets that say that the bug report is related to a certain component.
So we would put the name of the component as the label for that new ticket.</p>
<p>For routing incoming document access requests, we could put the label of the service that must treat the request, etc.</p>
<p><strong>Filtering</strong> is also a text classification problem: we can filter the content of emails to state whether they are spam or not.
And we can also use LLMs to filter harmful content from users&rsquo; inputs, and even classify the category of harm (hateful speech, harrasment, etc.)</p>
<h2 id="zero-shot-prompting-just-ask-the-model">Zero-shot prompting: just ask the model!</h2>
<p>What about just asking a large language model what it thinks the classification, or the label should be?
And indeed, LLMs are often very smart and can figure out the correct classification, without being trained specifically for that purpose.</p>
<p>Let&rsquo;s illustrate this with a very common type of text classification: <strong>sentiment analysis</strong>.</p>
<p>First, we can define an <code>enum</code> representing the various sentiments that can be recognized:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">enum</span><span style="color:#bbb"> </span>Sentiment<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>POSITIVE,<span style="color:#bbb"> </span>NEUTRAL,<span style="color:#bbb"> </span>NEGATIVE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We create a <code>record</code> which will hold the result of the sentiment analysis:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">SentimentClassification</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Sentiment<span style="color:#bbb"> </span>sentiment<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We will also need an <code>interface</code> to represent the type-safe Java service that the developers integrating this LLM-backed solution will call to retrieve the sentiment of the text:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">SentimentClassifier</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SentimentClassification<span style="color:#bbb"> </span><span style="color:#06287e">classify</span>(String<span style="color:#bbb"> </span>text);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice that it takes in input an unstructured <code>String</code> text, but in output, you&rsquo;ll manipulate a strongly typed object, not just a mere string.</p>
<p>It&rsquo;s time to prepare our Gemini model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;sentiment&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">addAllEnum</span>(Stream.<span style="color:#4070a0">of</span>(Sentiment.<span style="color:#4070a0">values</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">map</span>(Enum::name)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">collect</span>(Collectors.<span style="color:#4070a0">toList</span>()))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;re taking advantage of the latest feature of Gemini and LangChain4j, which permits to specify that we want 100% valid JSON in output,
and even better than this, we want the generated JSON output to comply with a JSON schema!</p>
<p>Now we create the sentiment analysis service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>SentimentClassifier<span style="color:#bbb"> </span>sentimentClassifier<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiServices.<span style="color:#4070a0">create</span>(SentimentClassifier.<span style="color:#4070a0">class</span>,<span style="color:#bbb"> </span>model);<span style="color:#bbb">
</span></span></span></code></pre></div><p>And we call it to retrieve the sentiment of the text we want to analyze:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>SentimentClassification<span style="color:#bbb"> </span>classification<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>sentimentClassifier.<span style="color:#4070a0">classify</span>(<span style="color:#4070a0">&#34;I am happy!&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(classification.<span style="color:#4070a0">sentiment</span>());<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// POSITIVE</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>We didn&rsquo;t even need to give Gemini examples, this is why it&rsquo;s called <em>zero-shot prompting</em>.
LLMs are usually smart enough to easily handle familiar classification tasks like sentiment analysis.</p>
<h2 id="few-shot-prompting-when-the-model-needs-a-little-help">Few-shot prompting: when the model needs a little help</h2>
<p>A more common approach with LLMs for text classification is <em>few-shot prompting</em>.
As the name implies, it&rsquo;s a prompting technique.</p>
<p>You give the model a task (classifying text), and you show it examples of classifications,
with a clear input/output format, to force the LLM to reply with just the expected class.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ChatLanguageModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxOutputTokens</span>(10)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxRetries</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>PromptTemplate<span style="color:#bbb"> </span>promptTemplate<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>PromptTemplate.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Analyze the sentiment of the text below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Respond only with one word to describe the sentiment.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: This is fantastic news!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT: POSITIVE
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: Pi is roughly equal to 3.14
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT: NEUTRAL
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: I hate disliked the pizza. Who&#39;d put pineapple toppings?
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT: NEGATIVE
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    INPUT: {{text}}
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    OUTPUT:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Prompt<span style="color:#bbb"> </span>prompt<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>promptTemplate.<span style="color:#4070a0">apply</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;text&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;I love strawberries!&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(prompt.<span style="color:#4070a0">toUserMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// POSITIVE</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>In the above approach, we use LangChain4j&rsquo;s <code>PromptTemplate</code>, with a placeholder value <code>{{text}}</code> that will contain the text to classify.
We don&rsquo;t use an <code>enum</code> value though, so we have to discriminate against a string in the end.
But we could also apply the same schema response handling as in our previous zero-shot example.</p>
<p>Let&rsquo;s rewrite this code a little bit differently, to <em>fake</em> a conversation with the model.
The model will see an exchange between a user and itself, and will also follow the same syntax, and will reply with just one word: the sentiment.
We&rsquo;ll use system instructions, and alternating AI and user messages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>ChatMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>fewShotPrompts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>List.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SystemMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Analyze the sentiment of the text below.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Respond only with one word to describe the sentiment.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;This is fantastic news!&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;POSITIVE&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;Pi is roughly equal to 3.14&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;NEUTRAL&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;I hate disliked the pizza. &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                     </span><span style="color:#4070a0">&#34;Who&#39;d put pineapple toppings?&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>AiMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;NEGATIVE&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;I love strawberries!&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(fewShotPrompts);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">text</span>());<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// POSITIVE</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Same outcome, stawberries are yummy!</p>
<h2 id="text-classification-with-embedding-models">Text classification with embedding models</h2>
<p>In the two previous sections, we took advantage of LLMs&rsquo; abilities to classify text on their own,
based on their intrinsic knowledge, or with the help of a few examples.
But there&rsquo;s another way we can investigate: <strong>using embedding vectors</strong> to compare texts.</p>
<p>Embedding vectors are mathematical representations of words/sentences/paragraphs, in the form of a vector of floating point values.
The way those vectors are calculated by <em>embedding models</em> makes those vector close to each other
(in terms of distance) when they are semantically close.
You can have a look at my recent article
<a href="https://glaforge.dev/posts/2024/07/02/the-power-of-embeddings-how-numbers-unlock-the-meaning-of-data/">introducing vector embeddings</a>.</p>
<p>LangChain4j provides a <code>TextClassifier</code> interface which allows to classify text, by comparing it to sets of other texts
that belong to a same class. So we give a map of possible labels, associated with lists of texts that belong to that category.</p>
<p>In particular, there&rsquo;s an <code>EmbeddingModelTextClassifier</code> that uses embedding models to compare the texts with the examples of each labels.
We can even tweak its internal algorithm to say whether we prefer if a text should be closer to the average of all the examples,
or if we prefer if it&rsquo;s closer to one of the examples (by default, it&rsquo;s half distance to the mean, and half distance to the closest example.)</p>
<p>So let&rsquo;s have a look at this solution.</p>
<p>Instead of doing sentiment analysis, we&rsquo;ll go with recipe classification: our goal will be to classify a recipe,
to know if it&rsquo;s an <em>appetizer</em>, a <em>main course</em>, or a <em>dessert</em>.</p>
<p>First, we need to define our labels, with an <code>enum</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">enum</span><span style="color:#bbb"> </span>DishType<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>APPETIZER,<span style="color:#bbb"> </span>MAIN,<span style="color:#bbb"> </span>DESSERT<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Because we don&rsquo;t have a dataset of recipes, we&rsquo;ll use Gemini to generate sample recipes, for each label.
For that, we need to configure Gemini:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>CHAT_MODEL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>We&rsquo;ll also configure an embedding model to calculate the vector embeddings:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>VertexAiEmbeddingModel<span style="color:#bbb"> </span>EMBEDDING_MODEL<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiEmbeddingModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;text-embedding-004&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">taskType</span>(VertexAiEmbeddingModel.<span style="color:#4070a0">TaskType</span>.<span style="color:#4070a0">CLASSIFICATION</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Vertex AI&rsquo;s embedding models are capable of handling various tasks, including:</p>
<ul>
<li><strong>classification</strong>,</li>
<li>semantic similarity,</li>
<li>clustering,</li>
<li>question answering,</li>
<li>fact verification,</li>
<li>query or document retrieval.</li>
</ul>
<p>Let&rsquo;s create a method to generate a recipe for a particular type of dish:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">recipeOf</span>(DishType<span style="color:#bbb"> </span>type)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>CHAT_MODEL.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Write a recipe for a %s dish&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">formatted</span>(type.<span style="color:#4070a0">name</span>().<span style="color:#4070a0">toLowerCase</span>()));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And we&rsquo;ll collect 3 examples of recipes for each type of dish:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>examplesOfRecipes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Stream.<span style="color:#4070a0">of</span>(DishType.<span style="color:#4070a0">values</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">collect</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Collectors.<span style="color:#4070a0">toMap</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>dishType<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>dishType,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>dishType<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>Stream.<span style="color:#4070a0">generate</span>(()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>recipeOf(dishType))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">limit</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>.<span style="color:#4070a0">toList</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>That way, we have our dataset ready, and we&rsquo;ll prepare a text classifier:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>EmbeddingModelTextClassifier<span style="color:#666">&lt;</span>DishType<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>recipeClassifier<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>EmbeddingModelTextClassifier<span style="color:#666">&lt;&gt;</span>(EMBEDDING_MODEL,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                                       </span>examplesOfRecipes);<span style="color:#bbb">
</span></span></span></code></pre></div><p>It takes a little while to calculate the initial embedding vectors of all the samples, but now our classifier is ready!
Let&rsquo;s see if the following recipe is an <em>appertizer</em>, a <em>main course</em>, or a <em>dessert</em>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>List<span style="color:#666">&lt;</span>DishType<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>classifiedDishes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>recipeClassifier.<span style="color:#4070a0">classify</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    **Classic Moist Chocolate Cake**
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    This recipe delivers a rich, moist chocolate cake that&#39;s
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    perfect for any occasion.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Ingredients:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 1 ¾ cups all-purpose flour
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 2 cups granulated sugar
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * ¾ cup unsweetened cocoa powder
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 1 ½ teaspoons baking powder
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 1 ½ teaspoons baking soda
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 1 teaspoon salt
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 2 large eggs
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 1 cup milk
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * ½ cup vegetable oil
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 2 teaspoons vanilla extract
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * 1 cup boiling water
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Instructions:
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Preheat oven to 350°F (175°C). Grease and flour two 9-inch
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      round cake pans.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Combine dry ingredients: In a large bowl, whisk together flour,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      sugar, cocoa powder, baking powder, baking soda, and salt.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Add wet ingredients: Beat in eggs, milk, oil, and vanilla until
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      combined.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Stir in boiling water: Carefully stir in boiling water. The
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      batter will be thin.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Bake: Pour batter evenly into prepared pans. Bake for 30-35
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      minutes, or until a toothpick inserted into the center comes
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      out clean.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    * Cool: Let cakes cool in pans for 10 minutes before transferring
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">      to a wire rack to cool completely.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;This recipe is of type: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>classifiedDishes);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// This recipe is of type: [DESSERT]</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>And voilà, we used the full power of embedding models to calculate text similarity to classify our chocolate cake recipe as a dessert!</p>
<h2 id="conclusion">Conclusion</h2>
<p>Large Language Models like Gemini are great at classifying text, thanks to their general knowledge of the world that they acquired during their training. But for more specialized use cases, we might need to guide the LLM to recognize labels, because the subject is very specific to our data. That&rsquo;s when few-shot prompting or embedding model-based classification helps.</p>
<p>If we have lots of samples for each label, using a few-shot prompting approach means we&rsquo;ll have to pass all those examples again and again in the context window of the LLM, which yields a high token count. So if you pay per tokens, it can become a bit expensive.</p>
<p>If we use the embedding model text classifier, it might take a while to compute all the embedding vectors, but we&rsquo;ll do it only once, and then we can just calculate the vector embedding for the text to classify, so it&rsquo;s just the tokens of the text to classify that is incurred.
If we have lots of samples, the classifier needs to do quite a few vector / matrix computations to calculate the distance to the samples, but it&rsquo;s usually quite fast (unless we really have hundreds or thousands of samples).</p>
<p>I hope this article showed you that Generative AI is useful beyond the usual chatbots and RAG use cases.
It&rsquo;s great at text classification as well.
And LangChain4j and Gemini are well suited for that use case, and you learned how to implement different approaches to do text classification.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Latest Gemini features support in LangChain4j 0.32.0</title><link>https://glaforge.dev/posts/2024/07/05/latest-gemini-features-support-in-langchain4j/</link><pubDate>Fri, 05 Jul 2024 11:53:30 +0200</pubDate><guid>https://glaforge.dev/posts/2024/07/05/latest-gemini-features-support-in-langchain4j/</guid><description>&lt;p>&lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a> 0.32.0 was released yesterday,
including my &lt;a href="https://github.com/langchain4j/langchain4j/pull/1278">pull request&lt;/a>
with the support for lots of new Gemini features:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>JSON output mode&lt;/strong>, to force Gemini to reply using JSON, without any markup,&lt;/li>
&lt;li>&lt;strong>JSON schema&lt;/strong>, to control and constrain the JSON output to comply with a schema,&lt;/li>
&lt;li>&lt;strong>Response grounding&lt;/strong> with Google Search web results and with private data in Vertex AI datastores,&lt;/li>
&lt;li>Easier debugging, thanks to new builder methods to &lt;strong>log requests and responses&lt;/strong>,&lt;/li>
&lt;li>&lt;strong>Function calling mode&lt;/strong> (none, automatic, or a subset of functions),&lt;/li>
&lt;li>&lt;strong>Safety settings&lt;/strong> to catch harmful prompts and responses.&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s explore those new features together, thanks to some code examples!
And at the end of the article, if you make it through, you&amp;rsquo;ll also discover &lt;strong>2 extra bonus points&lt;/strong>.&lt;/p></description><content:encoded>
<![CDATA[<p><a href="https://docs.langchain4j.dev/">LangChain4j</a> 0.32.0 was released yesterday,
including my <a href="https://github.com/langchain4j/langchain4j/pull/1278">pull request</a>
with the support for lots of new Gemini features:</p>
<ul>
<li><strong>JSON output mode</strong>, to force Gemini to reply using JSON, without any markup,</li>
<li><strong>JSON schema</strong>, to control and constrain the JSON output to comply with a schema,</li>
<li><strong>Response grounding</strong> with Google Search web results and with private data in Vertex AI datastores,</li>
<li>Easier debugging, thanks to new builder methods to <strong>log requests and responses</strong>,</li>
<li><strong>Function calling mode</strong> (none, automatic, or a subset of functions),</li>
<li><strong>Safety settings</strong> to catch harmful prompts and responses.</li>
</ul>
<p>Let&rsquo;s explore those new features together, thanks to some code examples!
And at the end of the article, if you make it through, you&rsquo;ll also discover <strong>2 extra bonus points</strong>.</p>
<h2 id="json-output-mode">JSON output mode</h2>
<p>Creating LLM-powered applications means working with text, as this is what LLMs return.
But to facilitate this integration between LLM responses and your code,
the text format of choice is usually JSON, as it&rsquo;s human-readable, and easy to parse programmatically.</p>
<p>However, LLMs are a bit chatty, and rather than sending you back a nice raw JSON document, instead, it replies with some extra sentence, and some markdown markup to wrap the piece of JSON.</p>
<p>Fortunately, Gemini 1.5 (Flash and Pro) allows you to specify the response MIME type.
Currently, only <code>application/json</code> is supported, but other formats may come later.</p>
<p>To do that, when instantiating the Gemini model, use the <code>responseMimeType()</code> builder method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseMimeType</span>(<span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Roll a dice&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span></code></pre></div><p>No sentence, no markdown markup, nothing, just pure JSON:</p>
<pre tabindex="0"><code>{&#34;roll&#34;: 3}
</code></pre><p>We didn&rsquo;t even need to say in the prompt we wanted to get a JSON response!</p>
<p>However, the JSON key of that document may vary from time to time, so you may still wish to be a bit more prescriptive in your prompt, and ask the model to return JSON explicitly, give it an example of the JSON output you expect, etc. That&rsquo;s the usual prompting approach&hellip;</p>
<p>But now there&rsquo;s more!</p>
<h2 id="json-schema-output">JSON Schema output</h2>
<p>This is quite unique in the LLM ecosystem, as I believe it&rsquo;s the only model out there that allows you to specify a JSON schema for constraining the JSON output.
This works for Gemini 1.5 Pro only, not with Gemini 1.5 Flash.</p>
<p>Let&rsquo;s have another look at our previous dice roll example, and let&rsquo;s update it to specify a JSON schema for the output generation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.SchemaHelper.fromClass</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">DiceRoll</span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>roll)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(<span style="color:#4070a0">&#34;genai-java-demos&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(fromClass(DiceRoll.<span style="color:#4070a0">class</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Roll a dice&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The generated JSON document will always contain the <code>roll</code> key</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{ <span style="color:#062873;font-weight:bold">&#34;roll&#34;</span>: <span style="color:#40a070">5</span> }
</span></span></code></pre></div><p>In this example, we used a convenience method called <code>fromClass()</code> that creates a JSON schema that corresponds to a Java type (here a Java record).</p>
<p>But there&rsquo;s also another convenient method that lets us pass a JSON schema string, called <code>fromJsonSchema()</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(<span style="color:#4070a0">&#34;genai-java-demos&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(fromJsonSchema(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;type&#34;: &#34;object&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            &#34;properties&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                &#34;roll&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                    &#34;type&#34;: &#34;integer&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>It&rsquo;s also possible to construct a JSON schema programmatically:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(<span style="color:#4070a0">&#34;genai-java-demos&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">responseSchema</span>(Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;roll&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">INTEGER</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now you always get consistent JSON outputs!</p>
<h2 id="response-grounding-with-google-search-web-results-and-vertex-ai-datastores">Response grounding with Google Search web results and Vertex AI datastores</h2>
<p>Large Language Models are wonderful creative machines, but rather than benefiting from their high degree of creativity, we&rsquo;d prefer having factual responses grounded on data and documents.</p>
<p>Gemini offers the ability to <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/ground-gemini">ground responses</a>:</p>
<ul>
<li>against Google Search web results,</li>
<li>against Vertex AI search datastores.</li>
</ul>
<h3 id="use-google-search-to-ground-responses">Use Google Search to ground responses</h3>
<p>The training of an LLM ended at a certain date: its <em>cut-off</em> date.
So it doesn&rsquo;t know about news that happened after that date.
But you can request Gemini to use Google Search to find more up-to-date information.</p>
<p>For example, if we ask Gemini about the current elections going on in France, it could reply with something like this:</p>
<pre tabindex="0"><code>There is no current national election happening in France right now.

The last major national election in France was the **Presidential
election in April and May 2022**, where Emmanuel Macron won a second
term.

There are, however, **local elections** happening regularly in
different regions of France.

To stay updated on French elections, you can check the website of
the **French Ministry of the Interior** or reputable news sources
like **The Guardian, BBC, CNN, or Le Monde**.
</code></pre><p>Now, let&rsquo;s enable the use of Google Search web result with the <code>useGoogleSearch(true)</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">useGoogleSearch</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;What is the current election going on in France?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The answer will be much different, and indeed factual and up-to-date:</p>
<pre tabindex="0"><code>France held the first round of a parliamentary election on July 4,
2024. The second round will be on July 7, 2024. The election is
significant because it could result in the first far-right government
in France since World War II.  The National Rally, President Emmanuel
Macron’s centrist alliance, and the New Popular Front coalition are
the three major political blocs competing in the election. The
outcome of the election is highly uncertain, with the far-right
National Rally potentially gaining a parliamentary majority.  If the
National Rally wins a majority, Macron would be expected to appoint
Jordan Bardella, the party&#39;s president, as prime minister.
</code></pre><p>There&rsquo;s indeed a parliamentary election going on right now in France.
Those elections were decided only a month ago, thus past the cut-of-date of the knowledge of the model.</p>
<blockquote>
<p>For my French audience, don&rsquo;t forget to go voting next Sunday!</p></blockquote>
<h3 id="grounding-with-vertex-ai-search">Grounding with Vertex AI Search</h3>
<p>The idea is that we want to ground responses on our own data.
This is particularly important when the knowledge required is actually private information, like our internal docs, or our customers&rsquo; docs.</p>
<p>My colleague Mete wrote a great
<a href="https://atamel.dev/posts/2024/07-01_grounding_with_own_data_vertexai_search/">article explaining how to setup grounding with private data</a>.
Below, I&rsquo;ll assume that we created a Vertex AI search app with a datastore backed by a Google Cloud Storage bucket that contains a fictious document which is a car manual, about the <em>Cymbel Starlight</em> car model! I&rsquo;m taking the same example as in Mete&rsquo;s article.</p>
<p>This time, we specify the search location to point at the Vertex AI search datastore with <code>vertexSearchDatastore()</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">vertexSearchDatastore</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;projects/%s/locations/%s/collections/%s/dataStores/%s&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>PROJECT_ID,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;global&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;default_collection&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;cymbal-datastore_1720169982142&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;What is the cargo capacity of Cymbal Starlight?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span></code></pre></div><p>It&rsquo;s a fictious car that doesn&rsquo;t exist, but it&rsquo;s covered in that private document, and indeed, Gemini is now able to respond to that question:</p>
<pre tabindex="0"><code>The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet.
</code></pre><p>What&rsquo;s interesting as well is that the response returned by Gemini provides some context about the source document that helped it answer the user query (we&rsquo;ll see in the next section how to enable logging requests and responses):</p>
<pre tabindex="0"><code>  grounding_metadata {
    2: {
      1: {
        3: 66
      }
      2: 0x3f7deee0
    }
    5: {
      2: {
        1: &#34;gs://genai-java-demos-documents/cymbal-starlight-2024.pdf&#34;
        2: &#34;cymbal-starlight-2024&#34;
      }
    }
    6: {
      1: {
        3: 66
        4: &#34;The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet.&#34;
      }
      2: &#34;\000&#34;
      3: {
        257772: 63
      }
    }
</code></pre><p>However, to be honest, I&rsquo;m not quite sure what the numbers exactly mean, but this metadata mentions that the PDF uploaded in cloud storage is the one that was used to shape the answer of the LLM, and gives an excerpt of the sentence that was found in the document.</p>
<h2 id="request-and-response-logging">Request and response logging</h2>
<p>To better understand what&rsquo;s going on under the hood, you can enable request and response logging.
That way, you&rsquo;re able to see exactly what is sent to Gemini, and what Gemini replies.</p>
<p>To enable logging, there are two methods we can use:</p>
<ul>
<li><code>logRequests(true)</code> to log the request sent to Gemini,</li>
<li><code>logResponse(true)</code> to log the response received from Gemini.</li>
</ul>
<p>Let&rsquo;s see that in action:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logRequests</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logResponses</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here&rsquo;s what&rsquo;s logged:</p>
<pre tabindex="0"><code>[main] DEBUG dev.langchain4j.model.vertexai.VertexAiGeminiChatModel -
 GEMINI (gemini-1.5-flash) request: InstructionAndContent {
 systemInstruction = null,
 contents = [role: &#34;user&#34;
parts {
  text: &#34;Why is the sky blue?&#34;
}
]
} tools: []


[main] DEBUG dev.langchain4j.model.vertexai.VertexAiGeminiChatModel -
 GEMINI (gemini-1.5-flash) response: candidates {
  content {
    role: &#34;model&#34;
    parts {
      text: &#34;The sky appears blue due to a phenomenon called
      **Rayleigh scattering**. Here\&#39;s a breakdown:\n\n* **Sunlight
      is made up of all colors of the rainbow.**  When sunlight enters
      the Earth\&#39;s atmosphere, it encounters tiny particles like
      nitrogen and oxygen molecules.\n* **These particles scatter the
      sunlight in all directions.**  However, shorter wavelengths of
      light, like blue and violet, scatter more strongly than longer
      wavelengths, like red and orange.\n* **This preferential
      scattering of shorter wavelengths is called Rayleigh
      scattering.**
      As a result, we see more blue light scattered throughout the sky,
      making it appear blue.\n\n**Why is the sky not violet?**\n\nEven
      though violet light scatters even more strongly than blue, our
      eyes are more sensitive to blue light. This is why we perceive
      the sky as blue rather than violet.\n\n**Other factors that
      affect sky color:**\n\n* **Time of day:** The sky appears more
      red or orange at sunrise and sunset because the sunlight has to
      travel through more of the atmosphere, scattering away most of
      the blue light.\n* **Clouds:** Clouds are made up of larger water
      droplets or ice crystals, which scatter all wavelengths of light
      equally. This is why clouds appear white.\n* **Pollution:**
      Pollution particles can scatter light differently, sometimes
      making the sky appear hazy or even reddish.\n\nLet me know if
      you have any other questions about the sky! \n&#34;
    }
  }
  finish_reason: STOP
  safety_ratings {
    category: HARM_CATEGORY_HATE_SPEECH
    probability: NEGLIGIBLE
    probability_score: 0.054802597
    severity: HARM_SEVERITY_NEGLIGIBLE
    severity_score: 0.03314852
  }
  safety_ratings {
    category: HARM_CATEGORY_DANGEROUS_CONTENT
    probability: NEGLIGIBLE
    probability_score: 0.100348406
    severity: HARM_SEVERITY_NEGLIGIBLE
    severity_score: 0.06359858
  }
  safety_ratings {
    category: HARM_CATEGORY_HARASSMENT
    probability: NEGLIGIBLE
    probability_score: 0.10837755
    severity: HARM_SEVERITY_NEGLIGIBLE
    severity_score: 0.021491764
  }
  safety_ratings {
    category: HARM_CATEGORY_SEXUALLY_EXPLICIT
    probability: NEGLIGIBLE
    probability_score: 0.10338596
    severity: HARM_SEVERITY_NEGLIGIBLE
    severity_score: 0.020410307
  }
}
usage_metadata {
  prompt_token_count: 6
  candidates_token_count: 288
  total_token_count: 294
}
</code></pre><p>Let me give you a bit more details about the logging. LangChain4j uses Slf4j by default for logging.
Request &amp; Response logging is logged at <code>DEBUG</code> level. So we have to configure our logger and/or logger façace accordingly.</p>
<p>In my test project for this article, I configured the following <code>Maven</code> dependencies for <code>Slf4j</code> and the <code>Simple</code> logger:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.slf4j<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>slf4j-api<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>2.0.13<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.slf4j<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>slf4j-simple<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>2.0.13<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>I created a properties file to configure the loggers: <code>src/main/resources/simplelogger.properties</code>, which contains the following configuration:</p>
<pre tabindex="0"><code>org.slf4j.simpleLogger.defaultLogLevel=debug
org.slf4j.simpleLogger.log.io.grpc.netty.shaded=info
</code></pre><p>I set the default logging level to be <code>debug</code>.
But there&rsquo;s also Netty, the networking library used under the hood by the Gemini Java SDK, that logs at debug level.
So I specified that the logging for this library should only be at <code>info</code> and above, otherwise the output is super chatty.</p>
<h2 id="function-calling-mode">Function calling mode</h2>
<p>So far, when using Gemini for
<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling">function calling</a>,
the model would decide on its own if a function would be useful to call, and which function to call.</p>
<p>But Gemini introduces the ability to
<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#tool-config">control the function or tool choice</a>.</p>
<p>There are 3 options:</p>
<ul>
<li><code>AUTO</code> — The familiar and default mode, where Gemini decides on its own if a function call is necessary and which one should be made,</li>
<li><code>ANY</code> — Allows to specify a subset of functions from all those available, but also forces the model to pick up one of them (only supported by Gemini 1.5 Pro),</li>
<li><code>NONE</code> — Even if tools are defined and available, prevents Gemini to use any of those tools.</li>
</ul>
<p>Let&rsquo;s have a look at this example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-pro&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logRequests</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logResponses</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">toolCallingMode</span>(ToolCallingMode.<span style="color:#4070a0">ANY</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">allowedFunctionNames</span>(Arrays.<span style="color:#4070a0">asList</span>(<span style="color:#4070a0">&#34;add&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ToolSpecification<span style="color:#bbb"> </span>adder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ToolSpecification.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">description</span>(<span style="color:#4070a0">&#34;adds two numbers&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">name</span>(<span style="color:#4070a0">&#34;add&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addParameter</span>(<span style="color:#4070a0">&#34;a&#34;</span>,<span style="color:#bbb"> </span>JsonSchemaProperty.<span style="color:#4070a0">INTEGER</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addParameter</span>(<span style="color:#4070a0">&#34;b&#34;</span>,<span style="color:#bbb"> </span>JsonSchemaProperty.<span style="color:#4070a0">INTEGER</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>UserMessage<span style="color:#bbb"> </span>message<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>UserMessage.<span style="color:#4070a0">from</span>(<span style="color:#4070a0">&#34;How much is 3 + 4?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>AiMessage<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>answer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(asList(message),<span style="color:#bbb"> </span>adder);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>answer.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">toolExecutionRequests</span>().<span style="color:#4070a0">getFirst</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>We specify the <code>ToolCallingMode.ANY</code> mode, and we list the allowed function names of the functions that the model must pick in order to reply to the request (with the <code>allowedFunctionNames()</code> builder method).</p>
<p>We describe the tool that can be called. We create a message.
And when calling <code>generate()</code>, we pass the tool specification corresponding to the function we want to be called.</p>
<p>The output will show that the model replied with the mandatory tool execution request:</p>
<pre tabindex="0"><code>ToolExecutionRequest { id = null, name = &#34;add&#34;,
                       arguments = &#34;{&#34;a&#34;:3.0,&#34;b&#34;:4.0}&#34; }
</code></pre><p>Now it&rsquo;s our turn to call the <code>add</code> function with the arguments.
And then send back the function execution result back to Gemini.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>Currently, it is not possible to use the <code>ANY</code> forced function calling mode when using LangChain4j&rsquo;s <code>AiServices</code> class.</p>
<p><code>AiServices</code> takes care of automatic function calling. But the process is a two-step request / response mechanism:</p>
<ul>
<li>First, we ask the model the math question and pass the tool specification along.</li>
<li>The model replies with a <code>ToolExecutionRequest</code>.</li>
<li>Then <code>AiServices</code> makes the function call locally, and replies to the model with the function execution result. However, since the <code>ANY</code> calling mode is specified at the model level, the model still wants to reply with yet another tool execution request. Although at this point, the second call made to the model was <em>just</em> to pass the function execution result, not to request another tool execution.</li>
<li>So <code>AiServices</code> enters an infite loop as the model requests a function execution again and again, not taking into account the execution result that it received.</li>
</ul>
<p>When using <code>AiServices</code>, it&rsquo;s better to let Gemini operate under the default <code>AUTO</code> tool mode.
So it knows when it needs to request a tool execution, or if just needs to handle the tool execution response.</p>
<p>If you want to use the <code>ANY</code> mode with <code>allowedFunctionNames()</code>, then don&rsquo;t use <code>AiServices</code>, and handle the function calls on your own in your code, to avoid such infite loop situations.</p>
      </div>
    </div><h2 id="specify-safety-settings">Specify safety settings</h2>
<p>In LLM-powered applications, where users can enter any kind of weird textual inputs,
you may want to limit harmful content that may be ingested.
To do so, you can specify some safety settings, for different categories of content, with different thresholds of acceptance:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.HarmCategory.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.SafetyThreshold.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">safetySettings</span>(Map.<span style="color:#4070a0">of</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>HARM_CATEGORY_DANGEROUS_CONTENT,<span style="color:#bbb"> </span>BLOCK_LOW_AND_ABOVE,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>HARM_CATEGORY_SEXUALLY_EXPLICIT,<span style="color:#bbb"> </span>BLOCK_MEDIUM_AND_ABOVE,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>HARM_CATEGORY_HARASSMENT,<span style="color:#bbb"> </span>BLOCK_ONLY_HIGH,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>HARM_CATEGORY_HATE_SPEECH,<span style="color:#bbb"> </span>BLOCK_MEDIUM_AND_ABOVE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>If you want to make your app safer for your end-users, and to avoid malicious or ill-disposed users, that&rsquo;s the way to go!</p>
<h2 id="bonus-point-1-streaming-responses-with-lambda-functions">Bonus point #1: Streaming responses with lambda functions</h2>
<p>I&rsquo;ll round up the review of Gemini-focused features with one little addition I contributed to the project:
the ability to pass a lambda instead of a streaming content handler, when using a streaming model.</p>
<p>This is not Gemini-related, you can use it with any model!</p>
<p>More concretely, if you want to use Gemini or another model in streaming mode, to see the response being printed as it&rsquo;s generated by the model, you would usually write the following code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiStreamingChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT_ID)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>StreamingResponseHandler<span style="color:#666">&lt;&gt;</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">onNext</span>(String<span style="color:#bbb"> </span>aFewTokens)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">print</span>(aFewTokens);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">onError</span>(Throwable<span style="color:#bbb"> </span>throwable)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>RuntimeException(throwable);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>});<span style="color:#bbb">
</span></span></span></code></pre></div><p>Using an anonymous inner class implementing the <code>StreamingResponseHandler</code> interface is quite verbose.
Fortunately, I contributed a couple static methods you can import, to make the code a little bit more concise:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.LambdaStreamingResponseHandler.onNext</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.LambdaStreamingResponseHandler.onNextAndError</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// onNext</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>onNext(System.<span style="color:#4070a0">out</span>::println));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">// onNextAndError</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>onNextAndError(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>::println,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ex<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>RuntimeException(ex);<span style="color:#bbb"> </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now you can stream your LLM output in a single instruction!</p>
<h2 id="bonus-point-2-generating-stunning-images-with-imagen-v3">Bonus point #2: Generating stunning images with Imagen v3</h2>
<p>A second bonus point in this new LangChain4j release is the fact that the Vertex AI Image model now supports
<a href="https://deepmind.google/technologies/imagen-3/">Imagen v3</a> (Google DeepMind&rsquo;s latest high-quality image generation model).</p>

    <div class="admonition warning">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Warning</span>
      </div>
      <div class="admonition-content">
        <p>To use the Imagen model, you&rsquo;ll still have to be allow-listed for now.
You&rsquo;ll need to <a href="https://docs.google.com/forms/d/1cqt9padvfMgqn23W5FMPTqh7bW1KLkEOsC5G6uC-uuM/viewform">fill this form</a>
to request access to the model.</p>
      </div>
    </div><p>There are a few new parameters that are available that you can take advantage of when generating pictures.
Let&rsquo;s have a look at the following image generation code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>imagenModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">publisher</span>(<span style="color:#4070a0">&#34;google&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagen-3.0-generate-preview-0611&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">aspectRatio</span>(VertexAiImageModel.<span style="color:#4070a0">AspectRatio</span>.<span style="color:#4070a0">LANDSCAPE</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">mimeType</span>(VertexAiImageModel.<span style="color:#4070a0">MimeType</span>.<span style="color:#4070a0">JPEG</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">compressionQuality</span>(80)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">watermark</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// true by default with Imagen v3</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">withPersisting</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logRequests</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">logResponses</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>prompt<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    An oil painting close-up, with heavy brush strokes full of
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    paint, of two hands shaking together, a young one, and an
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    old one conveying a sense of heartfelt thanks and connection
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    between generations
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imageResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>imagenModel.<span style="color:#4070a0">generate</span>(prompt);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(imageResponse.<span style="color:#4070a0">content</span>().<span style="color:#4070a0">url</span>());<span style="color:#bbb">
</span></span></span></code></pre></div><p>Let&rsquo;s see the resulting picture?</p>
<p><figure>
  <a href="#img-3a5a8cd81b0d62f1c0de3f16aadbc098">
    <img src="/img/gemini/imagen-v3-two-hands-shaking.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3a5a8cd81b0d62f1c0de3f16aadbc098">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/imagen-v3-two-hands-shaking.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In the code above, you certainly noticed the new builder methods:</p>
<ul>
<li><code>aspectRatio()</code> — not only square, but wide and narrow landscape and portrait modes are available,</li>
<li><code>mimeType()</code> — in addition to PNG, you can request JPEG image generation,</li>
<li><code>comressionQuality()</code> — when requesting JPEG, you can chose the level of compression for encoding the image,</li>
<li><code>watermark()</code> — to have all your generated images be watermarked with <a href="https://deepmind.google/technologies/synthid/">SynthId</a>,</li>
<li><code>logRequest()</code> / <code>logResponse()</code> — to see what is exchanged with the model, in and out,</li>
<li><code>persistToCloudStorage()</code> — to specify you want the image saved in a cloud storage bucket (not used in this example).</li>
</ul>
<p>If you get a chance, and request access to Imagen v3, you&rsquo;ll notice really great quality improvements compared to v2!</p>
<h2 id="conclusion">Conclusion</h2>
<p>Lots of new Gemini related features in this
<a href="https://github.com/langchain4j/langchain4j/releases/tag/0.32.0">release of LangChain4j</a>!
I hope this article helped you learn about them, and will make you want to use them in your projects.</p>
<p>If you want to go hands-on with Gemini with LangChain4j, don&rsquo;t forget to check out my self-paced codelab:
<a href="https://glaforge.dev/posts/2024/03/27/gemini-codelab-for-java-developers/">Gemini codelabg for Java developers, using LangChain4j</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The power of embeddings: How numbers unlock the meaning of data</title><link>https://glaforge.dev/posts/2024/07/02/the-power-of-embeddings-how-numbers-unlock-the-meaning-of-data/</link><pubDate>Tue, 02 Jul 2024 09:05:07 +0200</pubDate><guid>https://glaforge.dev/posts/2024/07/02/the-power-of-embeddings-how-numbers-unlock-the-meaning-of-data/</guid><description>&lt;h2 id="prelude">Prelude&lt;/h2>
&lt;blockquote>
&lt;p>As I&amp;rsquo;m focusing a lot on Generative AI, I&amp;rsquo;m curious about how things work under the hood, to better understand what I&amp;rsquo;m using in my gen-ai powered projects.
A topic I&amp;rsquo;d like to focus on more is: &lt;strong>vector embeddings&lt;/strong>, to explain more clearly what they are, how they are calculated, and what you can do with them.&lt;/p>
&lt;p>A colleague of mine, &lt;a href="https://x.com/andreban">André&lt;/a>, was showing me a &lt;a href="https://writer-m4n3dyfjhq-uc.a.run.app/">cool experiment&lt;/a>
he&amp;rsquo;s been working on, to help people prepare an interview, with the help of an AI, to shape the structure of the resulting final article to write.&lt;/p></description><content:encoded>
<![CDATA[<h2 id="prelude">Prelude</h2>
<blockquote>
<p>As I&rsquo;m focusing a lot on Generative AI, I&rsquo;m curious about how things work under the hood, to better understand what I&rsquo;m using in my gen-ai powered projects.
A topic I&rsquo;d like to focus on more is: <strong>vector embeddings</strong>, to explain more clearly what they are, how they are calculated, and what you can do with them.</p>
<p>A colleague of mine, <a href="https://x.com/andreban">André</a>, was showing me a <a href="https://writer-m4n3dyfjhq-uc.a.run.app/">cool experiment</a>
he&rsquo;s been working on, to help people prepare an interview, with the help of an AI, to shape the structure of the resulting final article to write.</p>
<p>The idea is to provide: a topic, a target audience, and to describe the goals for the audience.
Then, a large language model like <a href="https://deepmind.google/technologies/gemini/">Gemini</a> prepares a list of questions (that you can update freely) on that topic.
Next, it&rsquo;s your turn to fill in the blanks, answer those questions, and then the LLM generates an article,
with a plan following those key questions and your provided answers.
I cheated a bit, and asked <a href="https://gemini.google.com/">Gemini</a> itself those questions, and honestly, I really liked how the resulting article came to be,
and I wanted to share with you the outcome below.</p>
<p>It&rsquo;s a great and simple introduction to vector embeddings!
I like how AI can help organize information, shape the structure and the content for an article.
<strong>I&rsquo;m not advocating for letting AI write all your articles</strong>, far from that, but as an author,
however, I like that it can help me avoid the blank page syndrome, avoid missing key elements in my dissertation, improve the quality of my written prose.</p>
<p>Generative AI, in its creative aspect, and as your assistant, can be super useful! Use it as <strong>a tool to help drive your creativity</strong>!
But <strong>always use your critical sense to gauge the quality and factuality of the content</strong>.</p></blockquote>
<h2 id="introduction-what-are-vector-embeddings">Introduction: What are vector embeddings?</h2>
<p>Imagine you have a vast library filled with books on every topic imaginable. Finding a specific book can be a daunting task, especially if you only know the general subject matter. Now imagine a magical system that can understand the meaning of each book and represent it as a unique code. This code, called a vector embedding, can then be used to quickly find the most relevant books based on your search query, even if you only have a vague idea of what you&rsquo;re looking for.</p>
<p>This is the power of vector embeddings. They are essentially numerical representations of complex data, like text, images, or audio, that capture the underlying meaning and relationships within the data. These numerical codes, arranged as vectors, allow computers to process and compare data in a way that mimics human understanding.</p>
<h2 id="from-text-to-numbers-the-journey-of-embedding-creation">From Text to Numbers: The Journey of Embedding Creation</h2>
<p>Creating vector embeddings involves a multi-step process that transforms raw data into meaningful mathematical representations. The journey begins with <strong>data preprocessing</strong>, where the data is cleaned, normalized, and prepared for embedding generation. This might involve tasks like removing irrelevant information, standardizing data formats, and breaking text into individual words or subwords (tokenization).</p>
<p>Next comes the heart of the process: <strong>embedding generation</strong>. This step leverages various techniques and algorithms, such as Word2Vec, GloVe, BERT, and ResNet, to convert each data point into a high-dimensional vector. The specific algorithm chosen depends on the type of data being embedded (text, images, or audio) and the intended application.</p>
<p>For instance, Word2Vec uses a neural network to learn relationships between words by analyzing how they co-occur in large text corpora. This results in vector representations for words, where similar words have similar vectors, capturing semantic relationships. Similarly, for images, convolutional neural networks (CNNs) like ResNet can be used to extract features from images, resulting in vectors that represent the visual content.</p>
<h2 id="vector-databases-the-power-of-storing-and-searching-embeddings">Vector Databases: The Power of Storing and Searching Embeddings</h2>
<p>Once embeddings are generated, they need a dedicated storage system for efficient retrieval and comparison. This is where <strong>vector databases</strong> come into play. Unlike traditional databases designed for structured data, vector databases are optimized for storing and searching high-dimensional vector data.</p>
<p>Vector databases employ specialized indexing techniques, such as Annoy, HNSW, and Faiss, to create efficient data structures that allow for fast similarity search. This means that when a user submits a query (e.g., a search term, an image), the database can quickly find the most similar data points based on the similarity of their vector representations.</p>
<h2 id="embeddings-empower-search-finding-the-needle-in-the-haystack">Embeddings Empower Search: Finding the Needle in the Haystack</h2>
<p>The combination of vector embeddings and vector databases revolutionizes search by enabling <strong>semantic search</strong>. This means that instead of relying solely on keyword matching, search engines can understand the meaning behind the data and find relevant results even if the query doesn&rsquo;t use exact keywords.</p>
<p>For example, imagine searching for &ldquo;a picture of a dog with a hat.&rdquo; Traditional keyword-based search might struggle to find relevant images, as the search term might not match the image description. However, with vector embeddings, the search engine can understand the semantic meaning of the query and find images that contain both a dog and a hat, even if those words are not explicitly mentioned in the image description.</p>
<h2 id="beyond-search-expanding-the-reach-of-embeddings">Beyond Search: Expanding the Reach of Embeddings</h2>
<p>Vector embeddings are not limited to search applications. They have become essential tools in a wide range of fields, including:</p>
<ul>
<li><strong>Retrieval Augmented Generation (RAG):</strong> This technique combines the power of information retrieval and generative models to create more informative and relevant responses. Embeddings are used to find relevant information in large text corpora, which is then used to augment prompts for language models, resulting in more accurate and context-aware outputs.</li>
<li><strong>Data Classification:</strong> Embeddings enable the classification of data points into different categories based on their similarity. This finds application in areas like sentiment analysis, spam detection, object recognition, and music genre classification.</li>
<li><strong>Anomaly Detection:</strong> By representing data points as vectors, anomalies can be identified as data points that are significantly different from the majority. This technique is used in various fields, including network intrusion detection, fraud detection, and industrial sensor monitoring.</li>
</ul>
<h2 id="facing-the-challenges-and-shaping-the-future">Facing the Challenges and Shaping the Future</h2>
<p>While vector embeddings have revolutionized data analysis, they still face some challenges. These include the difficulty of capturing polysemy (multiple meanings of a word), contextual dependence, and the challenge of interpreting the meaning behind the high-dimensional vector representations.</p>
<p>Despite these limitations, research continues to push the boundaries of vector embeddings. Researchers are exploring techniques like contextual embeddings, multilingual embeddings, knowledge graph integration, and explainable embeddings to overcome existing limitations and unlock the full potential of these powerful representations.</p>
<h2 id="stepping-into-the-world-of-embeddings-resources-and-next-steps">Stepping into the World of Embeddings: Resources and Next Steps</h2>
<p>For those interested in diving deeper into the world of vector embeddings, a wealth of resources is available. Online courses and tutorials on platforms like Coursera, Fast.ai, and Stanford&rsquo;s online learning platform provide a solid foundation in the underlying concepts and techniques.</p>
<p>Books like &ldquo;Speech and Language Processing&rdquo; by Jurafsky and Martin and &ldquo;Deep Learning&rdquo; by Goodfellow, Bengio, and Courville offer in-depth coverage of the field. Additionally, research papers and articles on platforms like arXiv and Medium offer insights into the latest advancements and applications.</p>
<p>To gain practical experience, explore Python libraries like Gensim, spaCy, and TensorFlow/PyTorch. These libraries provide tools for creating and working with embeddings, allowing you to build your own models and experiment with various applications.</p>
<p>The world of vector embeddings is constantly evolving, offering exciting opportunities for innovation and discovery. By understanding the power of these representations, you can unlock new possibilities for data analysis, information retrieval, and artificial intelligence applications.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Functional builders in Java with Jilt</title><link>https://glaforge.dev/posts/2024/06/17/functional-builders-in-java-with-jilt/</link><pubDate>Mon, 17 Jun 2024 20:31:25 +0200</pubDate><guid>https://glaforge.dev/posts/2024/06/17/functional-builders-in-java-with-jilt/</guid><description>&lt;p>A few months ago, I shared an article about what I called Java
&lt;a href="https://glaforge.dev/posts/2024/01/16/java-functional-builder-approach/">functional builders&lt;/a>,
inspired by an equivalent pattern found in Go.
The main idea was to have builders that looked like this example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>LanguageModel&lt;span style="color:#bbb"> &lt;/span>languageModel&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#007020;font-weight:bold">new&lt;/span>&lt;span style="color:#bbb"> &lt;/span>LanguageModel(&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>name(&lt;span style="color:#4070a0">&amp;#34;cool-model&amp;#34;&lt;/span>),&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>project(&lt;span style="color:#4070a0">&amp;#34;my-project&amp;#34;&lt;/span>),&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>temperature(0.&lt;span style="color:#4070a0">5&lt;/span>),&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>description(&lt;span style="color:#4070a0">&amp;#34;This is a generative model&amp;#34;&lt;/span>)&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>);&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Compared to the more tranditional builder approach:&lt;/p>
&lt;ul>
&lt;li>You&amp;rsquo;re using the &lt;code>new&lt;/code> keyword again to construct instances.&lt;/li>
&lt;li>There&amp;rsquo;s no more &lt;code>build()&lt;/code> method, which felt a bit verbose.&lt;/li>
&lt;/ul>
&lt;p>Compared to using constructors with tons of parameters:&lt;/p></description><content:encoded>
<![CDATA[<p>A few months ago, I shared an article about what I called Java
<a href="https://glaforge.dev/posts/2024/01/16/java-functional-builder-approach/">functional builders</a>,
inspired by an equivalent pattern found in Go.
The main idea was to have builders that looked like this example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LanguageModel<span style="color:#bbb"> </span>languageModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>LanguageModel(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name(<span style="color:#4070a0">&#34;cool-model&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>project(<span style="color:#4070a0">&#34;my-project&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>temperature(0.<span style="color:#4070a0">5</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description(<span style="color:#4070a0">&#34;This is a generative model&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Compared to the more tranditional builder approach:</p>
<ul>
<li>You&rsquo;re using the <code>new</code> keyword again to construct instances.</li>
<li>There&rsquo;s no more <code>build()</code> method, which felt a bit verbose.</li>
</ul>
<p>Compared to using constructors with tons of parameters:</p>
<ul>
<li>You have methods like in traditional builders, that say what each parameter is about (<code>name()</code>, <code>temperature()</code>&hellip;)
a bit similar to named parameters in some programming languages.</li>
</ul>
<p>The approach I followed was to take advantage of lambda functions under the hood:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>ModelOption<span style="color:#bbb"> </span><span style="color:#06287e">temperature</span>(Float<span style="color:#bbb"> </span>temperature)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">temperature</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>temperature;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>However, there were a few downsides:</p>
<ul>
<li>Of course, it&rsquo;s not very conventional! So it can be a bit disturbing for people used to classical builders.</li>
<li>I didn&rsquo;t make the distinction between required and optional parameters (they were all optional!)</li>
<li>The internal fields were not <code>final</code>, and I felt they should be.</li>
</ul>
<h2 id="discovering-jilt">Discovering Jilt</h2>
<p>When searching on this topic, I found <a href="https://x.com/adam_ruka">Adam Ruka</a>&rsquo;s great annotation processor library:
<a href="https://github.com/skinny85/jilt">Jilt</a>.</p>
<p>One of the really cool features of Jilt is its staged builder concept,
which makes builders very type-safe, and forces you to call all the required property methods by chaining them.
I found this approach very elegant.</p>
<p>Adam heard about my functional builder approach, and decided to implement this new style of builder in Jilt.
There are a few differences with my implementation, but it palliates some of the downsides I mentioned.</p>
<p>Let&rsquo;s have a look at what functional builders looks like from a usage standpoint:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>LanguageModel<span style="color:#bbb"> </span>languageModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>languageModel(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name(<span style="color:#4070a0">&#34;cool-model&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>project(<span style="color:#4070a0">&#34;my-project&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>temperature(0.<span style="color:#4070a0">5</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description(<span style="color:#4070a0">&#34;This is a generative model&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Compared to my approach, you&rsquo;re not using constructors (as annotation processors can&rsquo;t change existing classes),
so you have to use a static method instead. But otherwise, inside that method call,
you have the named-parameter-like methods you&rsquo;re used to use in builders.</p>
<p>Here, <code>name()</code>, <code>project()</code> and <code>temperature()</code> are mandatory, and you&rsquo;d get a compilation error if you forgot one of them.
But <code>description()</code> is optional and can be ommitted.</p>
<p>Let&rsquo;s now look at the implementation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.jilt.Builder</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.jilt.BuilderStyle</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.jilt.Opt</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">jilt.testing.LanguageModelBuilder.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">jilt.testing.LanguageModelBuilder.Optional.description</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LanguageModel<span style="color:#bbb"> </span>languageModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>languageModel(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name(<span style="color:#4070a0">&#34;cool-model&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>project(<span style="color:#4070a0">&#34;my-project&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>temperature(0.<span style="color:#4070a0">5</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>description(<span style="color:#4070a0">&#34;This is a generative model&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Builder</span>(style<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>BuilderStyle.<span style="color:#4070a0">FUNCTIONAL</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">LanguageModel</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>name,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>project,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Double<span style="color:#bbb"> </span>temperature,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Opt</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>description<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I used a Java <code>record</code> but it could be a good old POJO.
You must annotate that class with the <code>@Builder</code> annotation.
The <code>style</code> parameter specifies that you want to use a <em>functional</em> builder.
Notice the use of the <code>@Opt</code> annotation to say that a parameter is not required.</p>
<h2 id="derived-instance-creation">Derived instance creation</h2>
<p>Let me close this article with another neat trick offered by Jilt, which is how to build other instances from existing ones:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Builder</span>(style<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>BuilderStyle.<span style="color:#4070a0">FUNCTIONAL</span>,<span style="color:#bbb"> </span>toBuilder<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;derive&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">record</span> <span style="color:#0e84b5;font-weight:bold">LanguageModel</span>(...)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>LanguageModel<span style="color:#bbb"> </span>derivedModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>derive(languageModel,<span style="color:#bbb"> </span>name(<span style="color:#4070a0">&#34;new-name&#34;</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>By adding the <code>toBuilder = &quot;derive&quot;</code> parameter to the annotation, you get the ability to create new instances
similar to the original one, but you can change both required and optional parameters, to derive a new instance.</p>
<h2 id="time-to-try-jilt">Time to try Jilt!</h2>
<p>You can try functional builders in <a href="https://github.com/skinny85/jilt">Jilt 1.6</a> which was just released a few days ago!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Let's make Gemini Groovy!</title><link>https://glaforge.dev/posts/2024/06/03/lets-make-gemini-groovy/</link><pubDate>Mon, 03 Jun 2024 11:49:26 +0200</pubDate><guid>https://glaforge.dev/posts/2024/06/03/lets-make-gemini-groovy/</guid><description>&lt;p>The happy users of &lt;a href="https://gemini.google.com/advanced">Gemini Advanced&lt;/a>,
the powerful AI web assistant powered by the Gemini model,
can execute some Python code, thanks to a built-in Python interpreter.
So, for math, logic, calculation questions, the assistant can let Gemini invent a Python script,
and execute it, to let users get a more accurate answer to their queries.&lt;/p>
&lt;p>But wearing my &lt;a href="https://groovy-lang.org/">Apache Groovy&lt;/a> hat on,
I wondered if I could get Gemini to invoke some Groovy scripts as well, for advanced math questions!&lt;/p></description><content:encoded>
<![CDATA[<p>The happy users of <a href="https://gemini.google.com/advanced">Gemini Advanced</a>,
the powerful AI web assistant powered by the Gemini model,
can execute some Python code, thanks to a built-in Python interpreter.
So, for math, logic, calculation questions, the assistant can let Gemini invent a Python script,
and execute it, to let users get a more accurate answer to their queries.</p>
<p>But wearing my <a href="https://groovy-lang.org/">Apache Groovy</a> hat on,
I wondered if I could get Gemini to invoke some Groovy scripts as well, for advanced math questions!</p>
<h2 id="langchain4j-based-approach">LangChain4j based approach</h2>
<p>As usual, my tool of choice for any LLM problem is the powerful <a href="https://docs.langchain4j.dev/">LangChain4j</a> framework!
Interestingly, there are already some code engine integrations,</p>
<ul>
<li>a <a href="https://www.graalvm.org/latest/reference-manual/polyglot-programming/">GraalVM Polyglot Truffle</a> engine, that can execute Python and JavaScript code,</li>
<li>a <a href="https://judge0.com/">Judge0</a> engine that uses the Judge0 online code execution system, which also supports Groovy!</li>
</ul>
<p>I haven&rsquo;t tried Judge0 yet, as I saw it was supporting Groovy 3 only, and not yet Groovy 4.
But for math or logic questions, Groovy 3 is just fine anyway.
Instead, I wanted to explore how to create my own Groovy interpreter!</p>
<p>In the following experiment, I&rsquo;m going to use the <a href="https://deepmind.google/technologies/gemini/">Gemini</a> model,
because it supports <em>function calling</em>, which means we can instruct the model that it can use some tools when needed.</p>
<p>Let&rsquo;s walk through this step by step.</p>
<p>First, I instantiate a Gemini chat model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(<span style="color:#4070a0">&#34;MY_GCP_PROJECT_ID&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxRetries</span>(1)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, I create a tool that is able to run Groovy code, thanks to the <code>GroovyShell</code> evaluator:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GroovyInterpreter</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Execute a Groovy script and return the result of its execution.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">executeGroovyScript</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@P</span>(<span style="color:#4070a0">&#34;The groovy script source code to execute&#34;</span>)<span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>groovyScript)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>script<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>groovyScript.<span style="color:#4070a0">replace</span>(<span style="color:#4070a0">&#34;\\n&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\n&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">err</span>.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;%n--&gt; Executing the following Groovy script:%n%s%n&#34;</span>,<span style="color:#bbb"> </span>script);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>Object<span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GroovyShell().<span style="color:#4070a0">evaluate</span>(script);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;result&#34;</span>,<span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;null&#34;</span><span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>result.<span style="color:#4070a0">toString</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(Throwable<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;error&#34;</span>,<span style="color:#bbb"> </span>e.<span style="color:#4070a0">getMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice the <code>@Tool</code> annotation that describes what this tool can do.
And the <code>@P</code> annotation which explains what the parameter is about.</p>
<p>I noticed that sometimes the raw script that Gemini suggested contained some <code>\n</code> strings,
instead of the plain newline characters, so I&rsquo;m replacing them with newlines instead.</p>
<p>I return a map containing either a result (as a string), or an error message if one was encountered.</p>
<p>Now it&rsquo;s time to create our assistant contract, in the form of an interface,
but with a very carefully crafted system instruction:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">GroovyAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#555;font-weight:bold">@SystemMessage</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    You are a problem solver equipped with the capability of \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    executing Groovy scripts.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    When you need to or you&#39;re asked to evaluate some math \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    function, some algorithm, or some code, use the \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    `executeGroovyScript` function, passing a Groovy script \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    that implements the function, the algorithm, or the code \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    that needs to be run.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    In the Groovy script, return a value. Don&#39;t print the result \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    to the console.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Don&#39;t use semicolons in your Groovy scripts, it&#39;s not necessary.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    When reporting the result of the execution of a script, \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    be sure to show the content of that script.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Call the `executeGroovyScript` function only once, \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    don&#39;t call it in a loop.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>String<span style="color:#bbb"> </span><span style="color:#06287e">chat</span>(String<span style="color:#bbb"> </span>msg);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This complex system instruction above tells the model what its role is,
and that it should call the provided Groovy script execution function
whenever it encounters the need to calculate some function, or execute some logic.</p>
<p>I also instruct it to return values instead of printing results.</p>
<p>Funnily, Gemini is a pretty decent Groovy programmer,
but it insists on always adding semi-colons like in Java,
so for a more <em>idiomatic</em> code style, I suggest it to get rid of them!</p>
<p>The final step is now to create our LangChain4j AI service with the following code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(GroovyAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatLanguageModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatMemory</span>(MessageWindowChatMemory.<span style="color:#4070a0">withMaxMessages</span>(20))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GroovyInterpreter())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>I combine the Gemini chat model, with a memory to keep track of users&rsquo; requests,
and the Groovy interpreter tool I&rsquo;ve just created.</p>
<p>Now let&rsquo;s see if Gemini is able to create and calculate a fibonacci function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>assistant.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;Write a `fibonacci` function, and calculate `fibonacci(18)`&#34;</span>));<span style="color:#bbb">
</span></span></span></code></pre></div><p>And the output is as follows:</p>
<blockquote>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> <span style="color:#06287e">fibonacci</span><span style="color:#666">(</span>n<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>n <span style="color:#666">&lt;=</span> <span style="color:#40a070">1</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> n
</span></span><span style="display:flex;"><span>  <span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">else</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> <span style="color:#06287e">fibonacci</span><span style="color:#666">(</span>n <span style="color:#666">-</span> <span style="color:#40a070">1</span><span style="color:#666">)</span> <span style="color:#666">+</span> fibonacci<span style="color:#666">(</span>n <span style="color:#666">-</span> <span style="color:#40a070">2</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>fibonacci<span style="color:#666">(</span><span style="color:#40a070">18</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>The result of executing the script is: 2584.</p></blockquote>
<h2 id="discussion">Discussion</h2>
<p>It took me a bit of time to find the right system instruction to get Groovy scripts that complied to my requirements.
However, I noticed sometimes some internal errors returned by the model, which I haven&rsquo;t fully understood
(and particularly why those happen at all)</p>
<p>On some occasions, I also noticed that LangChain4j keeps sending the same script for execution, in a loop.
Same thing: I still have to investigate why this rare behavior happens.</p>
<p>So this solution is a fun experiment, but I&rsquo;d call it just that, an experiment, as it&rsquo;s not as rock-solid as I want it to be.
But if I manage to make it more bullet-proof, maybe I could contribute it back as a dedicated execution engine for LangChain4j!</p>
<h2 id="full-source-code">Full source code</h2>
<p>Here&rsquo;s the full content of my experiment:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.agent.tool.P</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.agent.tool.Tool</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.memory.chat.MessageWindowChatMemory</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.VertexAiGeminiChatModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.service.AiServices</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.service.SystemMessage</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">groovy.lang.GroovyShell</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.Map</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GroovyCodeInterpreterAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">project</span>(<span style="color:#4070a0">&#34;MY_GCP_PROJECT_ID&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">location</span>(<span style="color:#4070a0">&#34;us-central1&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">maxRetries</span>(1)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GroovyInterpreter</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#555;font-weight:bold">@Tool</span>(<span style="color:#4070a0">&#34;Execute a Groovy script and return the result of its execution.&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">executeGroovyScript</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#555;font-weight:bold">@P</span>(<span style="color:#4070a0">&#34;The groovy script source code to execute&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>String<span style="color:#bbb"> </span>groovyScript)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">err</span>.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;%n--&gt; Raw Groovy script:%n%s%n&#34;</span>,<span style="color:#bbb"> </span>groovyScript);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>script<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>groovyScript.<span style="color:#4070a0">replace</span>(<span style="color:#4070a0">&#34;\\n&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;\n&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">err</span>.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;%n--&gt; Executing:%n%s%n&#34;</span>,<span style="color:#bbb"> </span>script);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>Object<span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GroovyShell().<span style="color:#4070a0">evaluate</span>(script);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;result&#34;</span>,<span style="color:#bbb"> </span>result<span style="color:#bbb"> </span><span style="color:#666">==</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span><span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;null&#34;</span><span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>result.<span style="color:#4070a0">toString</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(Throwable<span style="color:#bbb"> </span>e)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Map.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;error&#34;</span>,<span style="color:#bbb"> </span>e.<span style="color:#4070a0">getMessage</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">GroovyAssistant</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#555;font-weight:bold">@SystemMessage</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        You are a problem solver equipped with the capability of \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        executing Groovy scripts.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When you need to or you&#39;re asked to evaluate some math \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        function, some algorithm, or some code, use the \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        `executeGroovyScript` function, passing a Groovy script \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        that implements the function, the algorithm, or the code \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        that needs to be run.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        In the Groovy script, return a value. Don&#39;t print the result \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        to the console.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Don&#39;t use semicolons in your Groovy scripts, it&#39;s not necessary.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        When reporting the result of the execution of a script, \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        be sure to show the content of that script.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Call the `executeGroovyScript` function only once, \
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        don&#39;t call it in a loop.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>String<span style="color:#bbb"> </span><span style="color:#06287e">chat</span>(String<span style="color:#bbb"> </span>msg);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>assistant<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(GroovyAssistant.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">chatLanguageModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">chatMemory</span>(MessageWindowChatMemory.<span style="color:#4070a0">withMaxMessages</span>(20))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">tools</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GroovyInterpreter())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>assistant.<span style="color:#4070a0">chat</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;Write a `fibonacci` function, and calculate `fibonacci(18)`&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Grounding Gemini with Web Search results in LangChain4j</title><link>https://glaforge.dev/posts/2024/05/28/grounding-gemini-with-web-search-in-langchain4j/</link><pubDate>Tue, 28 May 2024 07:42:43 +0200</pubDate><guid>https://glaforge.dev/posts/2024/05/28/grounding-gemini-with-web-search-in-langchain4j/</guid><description>&lt;p>The latest &lt;a href="https://github.com/langchain4j/langchain4j/releases/tag/0.31.0">release of LangChain4j&lt;/a> (version 0.31) added the capability of &lt;em>grounding&lt;/em> large language models with results from web searches.
There&amp;rsquo;s an integration with
&lt;a href="https://developers.google.com/custom-search/v1/overview">Google Custom Search Engine&lt;/a>,
and also &lt;a href="https://tavily.com/">Tavily&lt;/a>.&lt;/p>
&lt;p>The fact of &lt;em>grounding&lt;/em> an LLM&amp;rsquo;s response with the results from a search engine
allows the LLM to find relevant information about the query from web searches,
which will likely include up-to-date information that the model won&amp;rsquo;t have seen
during its training, past its cut-off date when the training ended.&lt;/p></description><content:encoded>
<![CDATA[<p>The latest <a href="https://github.com/langchain4j/langchain4j/releases/tag/0.31.0">release of LangChain4j</a> (version 0.31) added the capability of <em>grounding</em> large language models with results from web searches.
There&rsquo;s an integration with
<a href="https://developers.google.com/custom-search/v1/overview">Google Custom Search Engine</a>,
and also <a href="https://tavily.com/">Tavily</a>.</p>
<p>The fact of <em>grounding</em> an LLM&rsquo;s response with the results from a search engine
allows the LLM to find relevant information about the query from web searches,
which will likely include up-to-date information that the model won&rsquo;t have seen
during its training, past its cut-off date when the training ended.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Remark</span>
      </div>
      <div class="admonition-content">
        <p>Gemini has a built-in <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/overview#ground-public">Google Web Search grounding</a>
capability, however, LangChain4j&rsquo;s Gemini integration doesn&rsquo;t yet surface this feature.
I&rsquo;m currently working on a pull request to support this.</p>
      </div>
    </div><h2 id="asking-questions-to-your-website">Asking questions to your website</h2>
<p>An interesting use case for LLM web search grounding is for example if you want to search a particular website.
I was interested in asking questions related to articles that I have posted on my personal website and blog.
Let&rsquo;s see, step by step, how you can implement this.</p>
<h3 id="creating-a-custom-search-engine">Creating a custom search engine</h3>
<p>First of all, as I decided to use Google Custom Search, I created a new custom search engine.
I won&rsquo;t detail the steps involved in this process, as it&rsquo;s explained in the <a href="https://developers.google.com/custom-search/docs/tutorial/creatingcse">documentation</a>.
I created a custom search searching only the content on my website: <a href="https://glaforge.dev">glaforge.dev</a>.
But you can potentially search the whole internet if you wish, or just your company website, etc.</p>
<p>Google Custom Search gave me an API key, as well as a Custom Search ID (csi) for my newly created custom search engine.
You can test the custom search engine with that ID with this URL:
<a href="https://programmablesearchengine.google.com/controlpanel/overview?cx=YOUR_CSI_HERE">https://programmablesearchengine.google.com/controlpanel/overview?cx=YOUR_CSI_HERE</a>.
It gives you a Google Search-like interface where you can enter your queries.
There&rsquo;s also a widget that you can integrate in your website if you wish.</p>
<h3 id="implementation">Implementation</h3>
<p>First of all, I configure the chat model I want to use.
I&rsquo;m using the latest and fastest Gemini model: <a href="https://deepmind.google/technologies/gemini/flash/">Gemini 1.5 Flash</a>.
I&rsquo;ve saved my Google Cloud project ID and locaction in environment variables.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next, I configure my web search engine.
Here, I&rsquo;m using Google Search, but it could be Tavily as well.
I also saved my API key and the ID of my custom web search in environment variables:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>WebSearchEngine<span style="color:#bbb"> </span>webSearchEngine<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleCustomWebSearchEngine.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CUSTOM_SEARCH_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">csi</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CUSTOM_SEARCH_CSI&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//    .logRequests(true)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//    .logResponses(true)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Note that you can log the requests and responses, for debugging purpose.</p>
<p>Next, I define a <em>content retriever</em>, this is a way to let LangChain4j know
that <em>content</em> can be <em>retrieved</em> from a particular tool or location:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ContentRetriever<span style="color:#bbb"> </span>contentRetriever<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>WebSearchContentRetriever.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">webSearchEngine</span>(webSearchEngine)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxResults</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now, I define the contract I want to use to interact with my Gemini model, by creating my own custom search <code>interface</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">SearchWebsite</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">search</span>(String<span style="color:#bbb"> </span>query);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This interface will be implemented by LangChain4j&rsquo;s <code>AiServices</code> system that binds several components together:
the chat language model (here, Gemini), and the web search content retriever I created above:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>SearchWebsite<span style="color:#bbb"> </span>website<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(SearchWebsite.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">chatLanguageModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">contentRetriever</span>(contentRetriever)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then I can ask my question to the LLM, which will find the relevant information in my blog:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>website.<span style="color:#4070a0">search</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;How can I call the Gemma model from LangChain4j?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;response = &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response);<span style="color:#bbb">
</span></span></span></code></pre></div><p>If I comment out the line <code>contentRetriever(contentRetriever)</code>, Gemini does a best effort at answering my question,
but since there&rsquo;s nothing in its training data (before its cut-off date)
about how to call the <a href="https://blog.google/technology/developers/gemma-open-models/">Gemma</a> model from LangChain4j,
it is not able to provide a useful answer.</p>
<p>But with the web search content retriever, Gemini is able to find the right material to ground its answer,
as the custom search returns my article on
<a href="https://glaforge.dev/posts/2024/04/04/calling-gemma-with-ollama-and-testcontainers/">calling Gemma with Ollama, Testcontainers, and LangChain4j</a>:</p>
<pre tabindex="0"><code>Based on the provided information, you can call the Gemma model from
LangChain4j using the following approach:

1. **Use Ollama:** The articles highlight Ollama as a tool for
interacting with Gemma. You would need to set up Ollama and ensure it
has access to the Gemma model.
2. **Integrate TestContainers:** TestContainers helps you manage
containerized environments for testing. You can use it to run Ollama
within a container alongside LangChain4j.
3. **Utilize LangChain4j:** LangChain4j provides the framework for
interacting with large language models. You would define your prompt,
send it to Ollama (which runs Gemma), and receive the response back
through LangChain4j.

**Example Steps:**

1. **Set up Ollama:** Install Ollama and configure it to use the
Gemma model.
2. **Create a Dockerfile:** Use a Dockerfile to define an image that
includes Ollama and any dependencies.
3. **Run Ollama in a container using TestContainers:** Start the
container using TestContainers and ensure it is accessible from your
LangChain4j code.
4. **Implement LangChain4j calls:** Use LangChain4j to construct your
prompt and send it to Ollama (which will pass it to Gemma).
5. **Receive and process the response:** Receive the generated response
from Gemma and process it as needed in your Java application.

**Note:** These steps provide a general approach. You will need to
refer to the documentation for Ollama, TestContainers, and LangChain4j
for specific implementation details.

This method leverages Ollama as an intermediary to access Gemma.
If you have access to Google&#39;s Gemini model directly, you might be
able to integrate it with LangChain4j without the Ollama step,
depending on the specific API or SDK offered by Google.
</code></pre><p>The LLM found that I have to use <a href="https://ollama.com/">Ollama</a> and
<a href="https://testcontainers.com/">TestContainers</a>, as explained in my article.
This information wasn&rsquo;t part of my query, so it proves that it really found the info in the article.</p>
<h2 id="discussion">Discussion</h2>
<p>The LLM based its answer on the <em>excerpts</em> contained in the search results,
not the whole content of the article, so some aspects of this answer are not totally correct:
For instance, you don&rsquo;t have to <em>install</em> Ollama or create your own <em>Dockerfile</em>.</p>
<p>To make the response perfect, I believe we would have to combine web search results
with Retrieval Augmented Generation, or pass the whole context of the article to the model,
so that it could provide a more thorough and factual answer.</p>
<p>For different queries that lead to shorter answers, the answer would probably be more to the point.</p>
<p>Another approach is to annotate our <code>String search(String query)</code> method with a <code>@SystemInstruction()</code>
with instructions that encourage the LLM to provide a shorter answer.
But it&rsquo;s difficult to find the right balance between too long and too short,
and of course without any sort of hallucinations!</p>
<p>For example, you can try with the following system instruction:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">SearchWebsite</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#555;font-weight:bold">@SystemMessage</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Provide a paragraph-long answer, not a long step by step explanation.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    Reply with &#34;I don&#39;t know the answer&#34; if the provided information isn&#39;t relevant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>String<span style="color:#bbb"> </span><span style="color:#06287e">search</span>(String<span style="color:#bbb"> </span>query);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I got the following response:</p>
<pre tabindex="0"><code>The provided information mentions using Gemma with Ollama,
TestContainers, and LangChain4j. You can use Ollama, a local
LLM server, and TestContainers, which provides lightweight,
disposable containers, to set up a testing environment.
Then, with LangChain4j, a Java library for interacting with LLMs,
you can call Gemma through the Ollama server.
</code></pre><p>Which is shorter and more factual, without being too short either!</p>
<h2 id="whats-next">What&rsquo;s next?</h2>
<p>In an upcoming article, I&rsquo;ll show you how to use Gemini&rsquo;s built-in Google Search grounding,
but first, I have to finish my pull request for the LangChain4j project!</p>
<p>Or I can explore how to reply more precisely to queries that lead to complex answers like the above,
maybe combinging a RAG approach to get the full context of the article found by the web search.</p>
<p>Also, the Tavily API seems to be able to return the raw content of the article,
so maybe it can help giving the LLM the full context of the article to base its answers on it.
So that may be worth comparing those two web search integrations too.</p>
<p>Stay tuned!</p>
<h2 id="full-sample-code">Full sample code</h2>
<p>For reference, here is the full sample (with the system instruction approach):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.VertexAiGeminiChatModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.rag.content.retriever.ContentRetriever</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.rag.content.retriever.WebSearchContentRetriever</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.service.AiServices</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.service.SystemMessage</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.web.search.WebSearchEngine</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">dev.langchain4j.web.search.google.customsearch.GoogleCustomWebSearchEngine</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GroundingWithSearch</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>VertexAiGeminiChatModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiGeminiChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">project</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;PROJECT_ID&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">location</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;LOCATION&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini-1.5-flash-001&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WebSearchEngine<span style="color:#bbb"> </span>webSearchEngine<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GoogleCustomWebSearchEngine.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">apiKey</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CUSTOM_SEARCH_API_KEY&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">csi</span>(System.<span style="color:#4070a0">getenv</span>(<span style="color:#4070a0">&#34;GOOGLE_CUSTOM_SEARCH_CSI&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//    .logRequests(true)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//    .logResponses(true)</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ContentRetriever<span style="color:#bbb"> </span>contentRetriever<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>WebSearchContentRetriever.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">webSearchEngine</span>(webSearchEngine)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">maxResults</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">SearchWebsite</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#555;font-weight:bold">@SystemMessage</span>(<span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Provide a paragraph-long answer, not a long step by step explanation.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        Reply with &#34;I don&#39;t know the answer&#34; if the provided information isn&#39;t relevant.
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>String<span style="color:#bbb"> </span><span style="color:#06287e">search</span>(String<span style="color:#bbb"> </span>query);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>SearchWebsite<span style="color:#bbb"> </span>website<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>AiServices.<span style="color:#4070a0">builder</span>(SearchWebsite.<span style="color:#4070a0">class</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">chatLanguageModel</span>(model)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">contentRetriever</span>(contentRetriever)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>website.<span style="color:#4070a0">search</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#4070a0">&#34;How can I call the Gemma model from LangChain4j?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;response = &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Gemini, Google's Large Language Model, for Java Developers</title><link>https://glaforge.dev/talks/2024/05/03/gemini-google-large-language-model-for-java-developers/</link><pubDate>Fri, 03 May 2024 09:35:08 +0200</pubDate><guid>https://glaforge.dev/talks/2024/05/03/gemini-google-large-language-model-for-java-developers/</guid><description>&lt;p>As a follow-up to my talk on &lt;a href="https://glaforge.dev/talks/2023/11/13/gen-ai-with-palm-2-and-java/">generative AI for Java developers&lt;/a>, I&amp;rsquo;ve developed a new presentation that focuses more on
the &lt;a href="https://deepmind.google/technologies/gemini/#introduction">Gemini&lt;/a> large multimodal model by Google.&lt;/p>
&lt;p>In this talk, we cover the multimodality capabilities of the model, as it&amp;rsquo;s able to ingest code, PDF, audio, video, and is able to reason about them.
Another specificity of Gemini is its huge context window of up to 1 million tokens!
This opens interesting perspectives, especially in multimodal scenarios.&lt;/p></description><content:encoded>
<![CDATA[<p>As a follow-up to my talk on <a href="https://glaforge.dev/talks/2023/11/13/gen-ai-with-palm-2-and-java/">generative AI for Java developers</a>, I&rsquo;ve developed a new presentation that focuses more on
the <a href="https://deepmind.google/technologies/gemini/#introduction">Gemini</a> large multimodal model by Google.</p>
<p>In this talk, we cover the multimodality capabilities of the model, as it&rsquo;s able to ingest code, PDF, audio, video, and is able to reason about them.
Another specificity of Gemini is its huge context window of up to 1 million tokens!
This opens interesting perspectives, especially in multimodal scenarios.</p>
<p>We also talk about the <a href="https://blog.google/technology/developers/gemma-open-models/">Gemma</a> model, a small open-weights model in the Gemini family, which I covered recently about how to <a href="https://glaforge.dev/posts/2024/04/04/calling-gemma-with-ollama-and-testcontainers/">run it locally thanks to Ollama and Testcontainers</a>.</p>
<p>In that presentation, I&rsquo;m showing some of my past Gemini-powered demos, as well as the code examples in my <a href="https://glaforge.dev/posts/2024/03/27/gemini-codelab-for-java-developers/">Gemini workshop for Java developers</a>, using <a href="https://docs.langchain4j.dev/">LangChain4j</a>.</p>
<script async class="speakerdeck-embed" data-id="202b1956e3b747afa85cbf5d1b40bf20" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Calling Gemma with Ollama, TestContainers, and LangChain4j</title><link>https://glaforge.dev/posts/2024/04/04/calling-gemma-with-ollama-and-testcontainers/</link><pubDate>Wed, 03 Apr 2024 19:02:01 +0200</pubDate><guid>https://glaforge.dev/posts/2024/04/04/calling-gemma-with-ollama-and-testcontainers/</guid><description>&lt;p>Lately, for my Generative AI powered Java apps,
I&amp;rsquo;ve used the &lt;a href="https://deepmind.google/technologies/gemini/#introduction">Gemini&lt;/a>
multimodal large language model from Google.
But there&amp;rsquo;s also &lt;a href="https://blog.google/technology/developers/gemma-open-models/">Gemma&lt;/a>,
its little sister model.&lt;/p>
&lt;p>Gemma is a family of lightweight, state-of-the-art open models built from the same research
and technology used to create the Gemini models. Gemma is available in two sizes: 2B and 7B.
Its weights are freely available, and its small size means you can run it on your own, even on your laptop.
So I was curious to give it a run with &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>Lately, for my Generative AI powered Java apps,
I&rsquo;ve used the <a href="https://deepmind.google/technologies/gemini/#introduction">Gemini</a>
multimodal large language model from Google.
But there&rsquo;s also <a href="https://blog.google/technology/developers/gemma-open-models/">Gemma</a>,
its little sister model.</p>
<p>Gemma is a family of lightweight, state-of-the-art open models built from the same research
and technology used to create the Gemini models. Gemma is available in two sizes: 2B and 7B.
Its weights are freely available, and its small size means you can run it on your own, even on your laptop.
So I was curious to give it a run with <a href="https://docs.langchain4j.dev/">LangChain4j</a>.</p>
<h2 id="how-to-run-gemma">How to run Gemma</h2>
<p>There are many ways to run Gemma: in the cloud,
via <a href="https://console.cloud.google.com/vertex-ai/publishers/google/model-garden/335">Vertex AI</a>
with a click of a button,
or <a href="https://cloud.google.com/kubernetes-engine/docs/tutorials/serve-gemma-gpu-vllm">GKE</a> with some GPUs,
but you can also run it locally with <a href="https://github.com/tjake/Jlama">Jlama</a> or
<a href="https://github.com/google/gemma.cpp">Gemma.cpp</a>.</p>
<p>Another good option is to run Gemma with <a href="https://ollama.com/">Ollama</a>,
a tool that you install on your machine, and which lets you run small models,
like Llama 2, Mistral, and <a href="https://ollama.com/library">many others</a>.
They quickly added support for <a href="https://ollama.com/library/gemma">Gemma</a> as well.</p>
<p>Once installed locally, you can run:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ollama run gemma:2b
</span></span><span style="display:flex;"><span>ollama run gemma:7b
</span></span></code></pre></div><p>Cherry on the cake, the <a href="">LangChain4j</a> library provides an
<a href="https://docs.langchain4j.dev/integrations/language-models/ollama">Ollama module</a>,
so you can plug Ollama supported models in your Java applications easily.</p>
<h2 id="containerization">Containerization</h2>
<p>After a great discussion with my colleague <a href="https://twitter.com/ddobrin">Dan Dobrin</a>
who had worked with Ollama and TestContainers
(<a href="https://github.com/GoogleCloudPlatform/serverless-production-readiness-java-gcp/blob/main/sessions/next24/books-genai-vertex-langchain4j/src/test/java/services/OllamaContainerTest.java">#1</a> and
<a href="https://github.com/GoogleCloudPlatform/serverless-production-readiness-java-gcp/blob/main/sessions/next24/books-genai-vertex-langchain4j/src/test/java/services/OllamaChatModelTest.java#L37">#2</a>)
in his <a href="https://github.com/GoogleCloudPlatform/serverless-production-readiness-java-gcp/tree/main">serverless production readiness workshop</a>, I decided to try the approach below.</p>
<p>Which brings us to the last piece of the puzzle:
Instead of having to install and run Ollama on my computer,
I decided to use Ollama within a container, handled by <a href="https://testcontainers.com/">TestContainers</a>.</p>
<p>TestContainers is not only useful for testing, but you can also use it for driving containers.
There&rsquo;s even a specific <a href="https://java.testcontainers.org/modules/ollama/">OllamaContainer</a> you can take advantage of!</p>
<p>So here&rsquo;s the whole picture:
<figure>
  <a href="#img-f2b4d2e607831def484498e9d744add3">
    <img src="/img/gemini/gemma-ollama-testcontainers-langchain4j.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f2b4d2e607831def484498e9d744add3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/gemma-ollama-testcontainers-langchain4j.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="time-to-implement-this-approach">Time to implement this approach!</h2>
<p>You&rsquo;ll find the code in the Github
<a href="https://github.com/glaforge/gemini-workshop-for-java-developers/blob/main/app/src/main/java/gemini/workshop/CallGemma.java">repository</a>
accompanying my recent <a href="https://codelabs.developers.google.com/codelabs/gemini-java-developers">Gemini workshop</a></p>
<p>Let&rsquo;s start with the easy part, interacting with an Ollama supported model with LangChain4j:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>OllamaContainer<span style="color:#bbb"> </span>ollama<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>createGemmaOllamaContainer();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ollama.<span style="color:#4070a0">start</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ChatLanguageModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>OllamaChatModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">baseUrl</span>(String.<span style="color:#4070a0">format</span>(<span style="color:#4070a0">&#34;http://%s:%d&#34;</span>,<span style="color:#bbb"> </span>ollama.<span style="color:#4070a0">getHost</span>(),<span style="color:#bbb"> </span>ollama.<span style="color:#4070a0">getFirstMappedPort</span>()))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemma:2b&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(response);<span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>You run an Ollama test container.</li>
<li>You create an Ollama chat model, by pointing at the address and port of the container.</li>
<li>You specify the model you want to use.</li>
<li>Then, you just need to call <code>model.generate(yourPrompt)</code> as usual.</li>
</ul>
<p>Easy?
Now let&rsquo;s have a look at the trickier part, my local method that creates the Ollama container:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// check if the custom Gemma Ollama image exists already</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>List<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>listImagesCmd<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>DockerClientFactory.<span style="color:#4070a0">lazyClient</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">listImagesCmd</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">withImageNameFilter</span>(TC_OLLAMA_GEMMA_2_B)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">exec</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(listImagesCmd.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Creating a new Ollama container with Gemma 2B image...&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>OllamaContainer<span style="color:#bbb"> </span>ollama<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>OllamaContainer(<span style="color:#4070a0">&#34;ollama/ollama:0.1.26&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ollama.<span style="color:#4070a0">start</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ollama.<span style="color:#4070a0">execInContainer</span>(<span style="color:#4070a0">&#34;ollama&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;pull&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;gemma:2b&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ollama.<span style="color:#4070a0">commitToImage</span>(TC_OLLAMA_GEMMA_2_B);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>ollama;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Using existing Ollama container with Gemma 2B image...&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// Substitute the default Ollama image with our Gemma variant</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>OllamaContainer(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>DockerImageName.<span style="color:#4070a0">parse</span>(TC_OLLAMA_GEMMA_2_B)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">asCompatibleSubstituteFor</span>(<span style="color:#4070a0">&#34;ollama/ollama&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You need to create a derived Ollama container that pulls in the Gemma model.
Either this image was already created beforehand, or if it doesn&rsquo;t exist yet, you create it.</p>
<p>Use the Docker Java client to check if the custom Gemma image exists.
If it doesn&rsquo;t exist, notice how TestContainers let you create an image derived from the base Ollama image,
pull the Gemma model, and then commit that image to your local Docker registry.</p>
<p>Otherwise, if the image already exists (ie. you created it in a previous run of the application),
you&rsquo;re just going to tell TestContainers that you want to substitute the default Ollama image
with your Gemma-powered variant.</p>
<h2 id="and-voila">And voila!</h2>
<p>You can <strong>call Gemma locally on your laptop, in your Java apps, using LangChain4j</strong>,
without having to install and run Ollama locally
(but of course, you need to have a Docker daemon running).</p>
<p>Big thanks to <a href="https://twitter.com/ddobrin">Dan Dobrin</a> for the approach,
and to <a href="https://twitter.com/bsideup">Sergei</a>, <a href="https://twitter.com/EdduMelendez">Eddú</a>
and <a href="https://twitter.com/shelajev">Oleg</a> from TestContainers for the help and useful pointers.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Gemini codelab for Java developers using LangChain4j</title><link>https://glaforge.dev/posts/2024/03/27/gemini-codelab-for-java-developers/</link><pubDate>Wed, 27 Mar 2024 19:11:58 +0100</pubDate><guid>https://glaforge.dev/posts/2024/03/27/gemini-codelab-for-java-developers/</guid><description>&lt;p>No need to be a Python developer to do Generative AI!
If you&amp;rsquo;re a Java developer, you can take advantage of &lt;a href="https://docs.langchain4j.dev/">LangChain4j&lt;/a>
to implement some advanced LLM integrations in your Java applications.
And if you&amp;rsquo;re interested in using
&lt;a href="https://blog.google/technology/ai/google-gemini-next-generation-model-february-2024/">Gemini&lt;/a>,
one of the best models available, I invite you to have a look at the following &amp;ldquo;codelab&amp;rdquo; that I worked on:&lt;/p>
&lt;p>&lt;a href="https://codelabs.developers.google.com/codelabs/gemini-java-developers">Codelab — Gemini for Java Developers using LangChain4j&lt;/a>&lt;/p>
&lt;p>In this workshop, you&amp;rsquo;ll find various examples covering the following use cases, in &lt;em>crescendo&lt;/em> approach:&lt;/p></description><content:encoded>
<![CDATA[<p>No need to be a Python developer to do Generative AI!
If you&rsquo;re a Java developer, you can take advantage of <a href="https://docs.langchain4j.dev/">LangChain4j</a>
to implement some advanced LLM integrations in your Java applications.
And if you&rsquo;re interested in using
<a href="https://blog.google/technology/ai/google-gemini-next-generation-model-february-2024/">Gemini</a>,
one of the best models available, I invite you to have a look at the following &ldquo;codelab&rdquo; that I worked on:</p>
<p><a href="https://codelabs.developers.google.com/codelabs/gemini-java-developers">Codelab — Gemini for Java Developers using LangChain4j</a></p>
<p>In this workshop, you&rsquo;ll find various examples covering the following use cases, in <em>crescendo</em> approach:</p>
<ul>
<li>Making your fist call to Gemini (streaming &amp; non-streaming)</li>
<li>Maintaining a conversation</li>
<li>Taking advantage of multimodality by analysing images with your prompts</li>
<li>Extracting structured information from unstructured text</li>
<li>Using prompt templates</li>
<li>Doing text classification with few-shot prompting</li>
<li>Implementing Retrieval Augmented Generation to chat with your documentation</li>
<li>How to do Function Calling to expand the LLM to interact with external APIs and services</li>
</ul>
<p>You&rsquo;ll find all the <a href="https://github.com/glaforge/gemini-workshop-for-java-developers">code samples on Github</a>.</p>
<p>If you&rsquo;re attending Devoxx France, be sure to attend the
<a href="https://www.devoxx.fr/en/schedule/talk/?id=40285">Hands-on-Lab workshop</a> with my colleagues
<a href="https://twitter.com/meteatamel">Mete Atamel</a> and <a href="https://twitter.com/val_deleplace">Valentin Deleplace</a>
who will guide you through this codelab.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Visualize PaLM-based LLM tokens</title><link>https://glaforge.dev/posts/2024/02/05/visualize-palm-based-llm-tokens/</link><pubDate>Mon, 05 Feb 2024 09:44:22 +0100</pubDate><guid>https://glaforge.dev/posts/2024/02/05/visualize-palm-based-llm-tokens/</guid><description>&lt;p>As I was working on tweaking the Vertex AI text embedding model in &lt;a href="https://github.com/langchain4j">LangChain4j&lt;/a>,
I wanted to better understand how the &lt;code>textembedding-gecko&lt;/code>
&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text-embeddings">model&lt;/a>
tokenizes the text, in particular when we implement the
&lt;a href="https://arxiv.org/abs/2005.11401">Retrieval Augmented Generation&lt;/a> approach.&lt;/p>
&lt;p>The various PaLM-based models offer a &lt;code>computeTokens&lt;/code> endpoint, which returns a list of tokens (encoded in Base 64)
and their respective IDs.&lt;/p>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition note">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">&lt;path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/>&lt;/svg>
&lt;span>Note&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>At the time of this writing, there&amp;rsquo;s no equivalent endpoint for Gemini models.&lt;/p></description><content:encoded>
<![CDATA[<p>As I was working on tweaking the Vertex AI text embedding model in <a href="https://github.com/langchain4j">LangChain4j</a>,
I wanted to better understand how the <code>textembedding-gecko</code>
<a href="https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text-embeddings">model</a>
tokenizes the text, in particular when we implement the
<a href="https://arxiv.org/abs/2005.11401">Retrieval Augmented Generation</a> approach.</p>
<p>The various PaLM-based models offer a <code>computeTokens</code> endpoint, which returns a list of tokens (encoded in Base 64)
and their respective IDs.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>At the time of this writing, there&rsquo;s no equivalent endpoint for Gemini models.</p>
      </div>
    </div><p>So I decided to create a <a href="https://tokens-lpj6s2duga-ew.a.run.app/">small application</a> that lets users:</p>
<ul>
<li>input some text,</li>
<li>select a model,</li>
<li>calculate the number of tokens,</li>
<li>and visualize them with some nice pastel colors.</li>
</ul>
<p>The available PaLM-based models are:</p>
<ul>
<li><code>textembedding-gecko</code></li>
<li><code>textembedding-gecko-multilingual</code></li>
<li><code>text-bison</code></li>
<li><code>text-unicorn</code></li>
<li><code>chat-bison</code></li>
<li><code>code-gecko</code></li>
<li><code>code-bison</code></li>
<li><code>codechat-bison</code></li>
</ul>
<p>You can <a href="https://tokens-lpj6s2duga-ew.a.run.app/">try the application</a> online.</p>
<p>And also have a look at the <a href="https://github.com/glaforge/llm-text-tokenization">source code</a> on Github.
It&rsquo;s a <a href="https://micronaut.io/">Micronaut</a> application.
I serve the static assets as explained in my recent
<a href="https://glaforge.dev/posts/2024/01/21/serving-static-assets-with-micronaut/">article</a>.
I deployed the application on <a href="https://cloud.run/">Google Cloud Run</a>,
the easiest way to deploy a container, and let it auto-scale for you.
I did a source based deployment, as explained at the bottom
<a href="https://glaforge.dev/posts/2022/10/24/build-deploy-java-17-apps-on-cloud-run-with-cloud-native-buildpacks-on-temurin/">here</a>.</p>
<p>And <em>voilà</em> I can visualize my LLM tokens!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Image generation with Imagen and LangChain4j</title><link>https://glaforge.dev/posts/2024/02/01/image-generation-with-imagen-and-langchain4j/</link><pubDate>Thu, 01 Feb 2024 09:25:56 +0100</pubDate><guid>https://glaforge.dev/posts/2024/02/01/image-generation-with-imagen-and-langchain4j/</guid><description>&lt;p>This week &lt;a href="https://github.com/langchain4j" title="LangChain4j">LangChain4j&lt;/a>, the LLM orchestration framework for Java developers, released version
&lt;a href="https://github.com/langchain4j/langchain4j/releases/tag/0.26.1" title="0.26.1">0.26.1&lt;/a>, which contains my first significant contribution to the open source project:
&lt;strong>support for the Imagen image generation model&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Imagen&lt;/strong> is a text-to-image diffusion model that was &lt;a href="https://imagen.research.google/" title="announced">announced&lt;/a> last year.
And it recently upgraded to &lt;a href="https://deepmind.google/technologies/imagen-2/" title="Imagen v2">Imagen v2&lt;/a>, with even higher quality graphics generation.
As I was curious to integrate it in some of my generative AI projects, I thought that would be a great first
&lt;a href="https://github.com/langchain4j/langchain4j/pull/456" title="contribution">contribution&lt;/a> to LangChain4j.&lt;/p></description><content:encoded>
<![CDATA[<p>This week <a href="https://github.com/langchain4j" title="LangChain4j">LangChain4j</a>, the LLM orchestration framework for Java developers, released version
<a href="https://github.com/langchain4j/langchain4j/releases/tag/0.26.1" title="0.26.1">0.26.1</a>, which contains my first significant contribution to the open source project:
<strong>support for the Imagen image generation model</strong>.</p>
<p><strong>Imagen</strong> is a text-to-image diffusion model that was <a href="https://imagen.research.google/" title="announced">announced</a> last year.
And it recently upgraded to <a href="https://deepmind.google/technologies/imagen-2/" title="Imagen v2">Imagen v2</a>, with even higher quality graphics generation.
As I was curious to integrate it in some of my generative AI projects, I thought that would be a great first
<a href="https://github.com/langchain4j/langchain4j/pull/456" title="contribution">contribution</a> to LangChain4j.</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition caution">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
        <span>Caution</span>
      </div>
      <div class="admonition-content">
        <p>At the time of this writing, image generation is still only for allow-listed accounts.</p>
<p>Furthermore, to run the snippets covered below, you should have an account on Google Cloud Platform,
created a project, configured a billing account, enabled the Vertex AI API,
and authenticated with the gcloud SDK and the command:
<code>gcloud auth application-default login</code>.</p>
      </div>
    </div><p>Now let&rsquo;s dive in how to use Imagen v1 and v2 with LangChain4j in Java!</p>
<h2 id="generate-your-first-images">Generate your first images</h2>
<p>In the following examples, I&rsquo;m using the following constants, to point at my project details, the endpoint, the region, etc:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>ENDPOINT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;us-central1-aiplatform.googleapis.com:443&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>LOCATION<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;us-central1&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>PROJECT<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;YOUR_PROJECT_ID&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">final</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>PUBLISHER<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;google&#34;</span>;<span style="color:#bbb">
</span></span></span></code></pre></div><p>First, we&rsquo;re going to create an instance of the model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiImageModel<span style="color:#bbb"> </span>imagenModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">project</span>(PROJECT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">publisher</span>(PUBLISHER)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagegeneration@005&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxRetries</span>(2)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">withPersisting</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 2 models you can use:</p>
<ul>
<li><code>imagegeneration@005</code> corresponds to Imagen 2</li>
<li><code>imagegeneration@002</code> is the previous version (Imagen 1)</li>
</ul>
<p>In this article, we&rsquo;ll use both models. Why? Because currently Imagen 2 doesn&rsquo;t support image editing, so we&rsquo;ll have to use Imagen 1 for that purpose.</p>
<p>The configuration above uses <code>withPersisting()</code> to save the generated images in a temporary folder on your system.
If you don&rsquo;t persist the image files, the content of the image is avaiable as Base 64 encoded bytes in the <code>Image</code>s objects returned.
You can also specify <code>persistTo(somePath)</code> to specify a particular directory where you want the generated files to be saved.</p>
<p>Let&rsquo;s create our first image:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imageResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>imagenModel.<span style="color:#4070a0">generate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;watercolor of a colorful parrot drinking a cup of coffee&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>Response</code> object wraps the created <code>Image</code>.
You can get the <code>Image</code> by calling <code>imageResponse.getContent()</code>.
And you can retrieve the URL of the image (if saved locally) with <code>imageResponse.getContent().url()</code>.
The Base 64 encoded bytes can be retrieved with <code>imageResponse.getContent().base64Data()</code></p>
<p>Some other tweaks to the model configuration:</p>
<ul>
<li>Specify the <strong>language</strong> of the prompt: <code>language(&quot;ja&quot;)</code>
(if the language is not officially supported, it&rsquo;s usually translated back to English anyway).</li>
<li>Define a <strong>negative prompt</strong> with things you don&rsquo;t want to see in the picture: <code>negativePrompt(&quot;black feathers&quot;)</code>.</li>
<li>Use a particular <strong>seed</strong> to always generate the same image with the same seed: <code>seed(1234L)</code>.</li>
</ul>
<p>So if you want to generate a picture of a pizza with a prompt in Japanese, but you don&rsquo;t want to have pepperoni and pineapple,
you could configure your model and generate as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiImageModel<span style="color:#bbb"> </span>imagenModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(PROJECT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(PUBLISHER)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagegeneration@005&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">language</span>(<span style="color:#4070a0">&#34;ja&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">negativePrompt</span>(<span style="color:#4070a0">&#34;pepperoni, pineapple&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxRetries</span>(2)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">withPersisting</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imageResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>imagenModel.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;ピザ&#34;</span>);<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic">// pizza</span><span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="image-editing-with-imagen-1">Image editing with Imagen 1</h2>
<p>With Imagen 1, you can <a href="https://cloud.google.com/vertex-ai/docs/generative-ai/image/edit-images?hl=en" title="edit">edit</a> existing images:</p>
<ul>
<li><strong>mask-based editing:</strong> you can specify a mask, a black &amp; white image where the white parts are the corresponding parts of the original image that should be edited,</li>
<li><strong>mask free editing:</strong> where you just give a prompt and let the model figure out what should be edited on its own or following the prompt.</li>
</ul>
<p>When generating and editing with Imagen 1, you can also configure the model to use a particular style (with Imagen 2, you just specify it in the prompt) with <code>sampleImageStyle(VertexAiImageModel.ImageStyle.photograph)</code>:</p>
<ul>
<li><code>photograph</code></li>
<li><code>digital_art</code></li>
<li><code>landscape</code></li>
<li><code>sketch</code></li>
<li><code>watercolor</code></li>
<li><code>cyberpunk</code></li>
<li><code>pop_art</code></li>
</ul>
<p>When editing an image, you may wish to decide how strong or not the modification should be, with <code>.guidanceScale(100)</code>.
Usually, between 0 and 20 or so, it&rsquo;s lightly edited, between 20 and 100 it&rsquo;s getting more impactful edits, and 100 and above it&rsquo;s the maximum edition level.</p>
<p>Let&rsquo;s say I generated an image of a lush forrest (I&rsquo;ll use that as my original image):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiImageModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(PROJECT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(PUBLISHER)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagegeneration@002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">seed</span>(19707L)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">sampleImageStyle</span>(VertexAiImageModel.<span style="color:#4070a0">ImageStyle</span>.<span style="color:#4070a0">photograph</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">guidanceScale</span>(100)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxRetries</span>(4)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">withPersisting</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>forestResp<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;lush forest&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now I want to edit my forrest to add a small red tree in the bottom of the image.
I&rsquo;m loading a black and white mask image with a white square at the bottom.
And I pass the original image, the mask image, and the modification prompt, to the new <code>edit()</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>URI<span style="color:#bbb"> </span>maskFileUri<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>getClass().<span style="color:#4070a0">getClassLoader</span>().<span style="color:#4070a0">getResource</span>(<span style="color:#4070a0">&#34;mask.png&#34;</span>).<span style="color:#4070a0">toURI</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>compositeResp<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">edit</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>forestResp.<span style="color:#4070a0">content</span>(),<span style="color:#bbb">              </span><span style="color:#60a0b0;font-style:italic">// original image to edit</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>fromPath(Paths.<span style="color:#4070a0">get</span>(maskFileUri)),<span style="color:#bbb">  </span><span style="color:#60a0b0;font-style:italic">// the mask image</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#4070a0">&#34;red trees&#34;</span><span style="color:#bbb">                        </span><span style="color:#60a0b0;font-style:italic">// the new prompt</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p><figure>
  <a href="#img-9210c97687e9e4cf920605a81813d109">
    <img src="/img/gemini/lush-forrest-red-tree.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9210c97687e9e4cf920605a81813d109">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gemini/lush-forrest-red-tree.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Another kind of editing you can do is to upscale an existing image.
As far as I know, it&rsquo;s only supported for Imagen v1 for now, so we&rsquo;ll continue with that model.</p>
<p>In this example, we&rsquo;ll generate an image of 1024x1024 pixels, and we&rsquo;ll scale it to 4096x4096:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>VertexAiImageModel<span style="color:#bbb"> </span>imagenModel<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(PROJECT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(PUBLISHER)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagegeneration@002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">sampleImageSize</span>(1024)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">withPersisting</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">persistTo</span>(defaultTempDirPath)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxRetries</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>imageResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>imagenModel.<span style="color:#4070a0">generate</span>(<span style="color:#4070a0">&#34;A black bird looking itself in an antique mirror&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>VertexAiImageModel<span style="color:#bbb"> </span>imagenModelForUpscaling<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>VertexAiImageModel.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">endpoint</span>(ENDPOINT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">location</span>(LOCATION)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">project</span>(PROJECT)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">publisher</span>(PUBLISHER)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;imagegeneration@002&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">sampleImageSize</span>(4096)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">withPersisting</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">persistTo</span>(defaultTempDirPath)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">maxRetries</span>(3)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Response<span style="color:#666">&lt;</span>Image<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>upscaledImageResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>imagenModelForUpscaling.<span style="color:#4070a0">edit</span>(imageResponse.<span style="color:#4070a0">content</span>(),<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>And now you have a much bigger image!</p>
<h2 id="conclusion">Conclusion</h2>
<p>That&rsquo;s about it for image generation and editing with Imagen in LangChain4j today!
Be sure to use LangChain4j v0.26.1 which contains that new integration.
And I&rsquo;m looking forward to seeing the pictures you generate with it!
m</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serving static assets with Micronaut</title><link>https://glaforge.dev/posts/2024/01/21/serving-static-assets-with-micronaut/</link><pubDate>Sun, 21 Jan 2024 17:23:25 +0100</pubDate><guid>https://glaforge.dev/posts/2024/01/21/serving-static-assets-with-micronaut/</guid><description>&lt;p>My go-to framework when developing Java apps or microservices is
&lt;a href="https://micronaut.io">Micronaut&lt;/a>.
For the apps that should have a web frontend, I rarely use
&lt;a href="https://micronaut-projects.github.io/micronaut-views/latest/guide/">Micronaut Views&lt;/a>
and its templating support.
Instead, I prefer to just &lt;strong>serve static assets&lt;/strong> from my resource folder,
and have some JavaScript framework (usually &lt;a href="https://vuejs.org/">Vue.js&lt;/a>)
to populate my HTML content (often using
&lt;a href="https://shoelace.style/">Shoelace&lt;/a> for its nice Web Components).
However, the &lt;a href="https://docs.micronaut.io/latest/guide/#staticResources">static asset documentation&lt;/a>
is a bit light on explanations.
So, since I always forget how to configure Micronaut to serve static assets,
I thought that would be useful to document this here.&lt;/p></description><content:encoded>
<![CDATA[<p>My go-to framework when developing Java apps or microservices is
<a href="https://micronaut.io">Micronaut</a>.
For the apps that should have a web frontend, I rarely use
<a href="https://micronaut-projects.github.io/micronaut-views/latest/guide/">Micronaut Views</a>
and its templating support.
Instead, I prefer to just <strong>serve static assets</strong> from my resource folder,
and have some JavaScript framework (usually <a href="https://vuejs.org/">Vue.js</a>)
to populate my HTML content (often using
<a href="https://shoelace.style/">Shoelace</a> for its nice Web Components).
However, the <a href="https://docs.micronaut.io/latest/guide/#staticResources">static asset documentation</a>
is a bit light on explanations.
So, since I always forget how to configure Micronaut to serve static assets,
I thought that would be useful to document this here.</p>
<p>In <code>/src/main/resources/application.properties</code>, I&rsquo;m adding the following:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-properties" data-lang="properties"><span style="display:flex;"><span><span style="color:#4070a0">micronaut.router.static-resources.default.paths</span><span style="color:#666">=</span><span style="color:#4070a0">classpath:public</span>
</span></span><span style="display:flex;"><span><span style="color:#4070a0">micronaut.router.static-resources.default.mapping</span><span style="color:#666">=</span><span style="color:#4070a0">/**</span>
</span></span><span style="display:flex;"><span><span style="color:#4070a0">micronaut.router.static-resources.default.enabled</span><span style="color:#666">=</span><span style="color:#4070a0">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#4070a0">micronaut.server.cors.enabled</span><span style="color:#666">=</span><span style="color:#4070a0">true</span>
</span></span></code></pre></div><ul>
<li>The first line says that my resources will live in <code>src/main/resources/public/</code>.</li>
<li>The second line means the pattern will match recursively for sub-directories as well.</li>
<li>The <code>enabled</code> flag is to activate static serviing (not strictly needed as it&rsquo;s supposed to be enabled by default).</li>
<li>I also enabled CORS (cross-origin resource sharing).</li>
</ul>
<p>Then in <code>src/main/resources/public/</code>, I&rsquo;ll have my <code>index.html</code> file,
my <code>css</code> and <code>js</code> folders.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Light Mode Bookmarlet</title><link>https://glaforge.dev/posts/2024/01/18/light-mode-bookmarlet/</link><pubDate>Thu, 18 Jan 2024 09:49:01 +0100</pubDate><guid>https://glaforge.dev/posts/2024/01/18/light-mode-bookmarlet/</guid><description>&lt;p>A while ago, my friend Sylvain Wallez shared a little &lt;a href="https://twitter.com/bluxte/status/1729912211882094701" title="bookmarlet">bookmarlet&lt;/a>&lt;br />
on Twitter/X that transforms a dark mode site into light mode.&lt;br />
I know the trend is towards dark mode, but for a lot of people with certain vision issues,&lt;br />
for example with astigmatism like me, certain dark modes can very painful.&lt;/p>
&lt;p>This site about &lt;a href="https://www.allaboutvision.com/digital-eye-strain/is-dark-mode-better-for-eyes/" title="vision">vision&lt;/a>&lt;br />
(and you&amp;rsquo;ll find other similar references) mentions that:&lt;/p>
&lt;blockquote>
&lt;p>People who have myopia or &lt;strong>astigmatism&lt;/strong> also may experience &lt;strong>halation&lt;/strong> (from the word “halo”).&lt;br />
Halation occurs when light spreads past a certain boundary, creating a foggy or blurry appearance.&lt;/p></description><content:encoded>
<![CDATA[<p>A while ago, my friend Sylvain Wallez shared a little <a href="https://twitter.com/bluxte/status/1729912211882094701" title="bookmarlet">bookmarlet</a><br />
on Twitter/X that transforms a dark mode site into light mode.<br />
I know the trend is towards dark mode, but for a lot of people with certain vision issues,<br />
for example with astigmatism like me, certain dark modes can very painful.</p>
<p>This site about <a href="https://www.allaboutvision.com/digital-eye-strain/is-dark-mode-better-for-eyes/" title="vision">vision</a><br />
(and you&rsquo;ll find other similar references) mentions that:</p>
<blockquote>
<p>People who have myopia or <strong>astigmatism</strong> also may experience <strong>halation</strong> (from the word “halo”).<br />
Halation occurs when light spreads past a certain boundary, creating a foggy or blurry appearance.</p></blockquote>
<p>So for certain websites, often with a too strong contrast, I&rsquo;m using the following bookmarklet trick.</p>
<p>Go to your bookmark manager, and save the following bookmarklet (I called mine &ldquo;light mode&rdquo;):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>javascript<span style="color:#666">:</span>(<span style="color:#007020;font-weight:bold">function</span>(){<span style="color:#007020">document</span>.documentElement.style.filter<span style="color:#666">=</span><span style="color:#007020">document</span>.documentElement.style.filter<span style="color:#666">?%</span><span style="color:#40a070">27</span><span style="color:#666">%</span><span style="color:#40a070">27</span><span style="color:#666">:%</span><span style="color:#40a070">27</span>invert(<span style="color:#40a070">100</span><span style="color:#666">%</span>)<span style="color:#666">%</span><span style="color:#40a070">20</span>hue<span style="color:#666">-</span>rotate(<span style="color:#40a070">180</span>deg)<span style="color:#666">%</span><span style="color:#40a070">27</span>})();
</span></span></code></pre></div><p>Now, to pretty print the above code and remove the URL encoded characters, to decypher what it does:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>(<span style="color:#007020;font-weight:bold">function</span> () {
</span></span><span style="display:flex;"><span>  <span style="color:#007020">document</span>.documentElement.style.filter <span style="color:#666">=</span> <span style="color:#007020">document</span>.documentElement.style.filter
</span></span><span style="display:flex;"><span>    <span style="color:#666">?</span> <span style="color:#4070a0">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">:</span> <span style="color:#4070a0">&#34;invert(100%) hue-rotate(180deg)&#34;</span>;
</span></span><span style="display:flex;"><span>})();
</span></span></code></pre></div><p>Two filters are going to be applied to your current web page:</p>
<ul>
<li>First, it will completely <strong>invert</strong> all the colors, like a negative photography</li>
<li>Second, compared to Sylvain, I also add a <strong>hue rotation</strong> of 180 degrees</li>
</ul>
<h2 id="why-the-hue-rotation">Why the hue rotation</h2>
<p>Because the color inversion is also going to shift the colors: a red will become blue, a yellow will be dark blue, a violet will turn pink, etc.<br />
With a hue rotation, we get back the right color, a red is still red, a blue is still blue, etc.<br />
The different however will be in the lightness, as a light blue becomes dark, and a dark green becomes light.<br />
But at least, it&rsquo;s a bit more faithful to the original images.</p>
<p>Here&rsquo;s a picture to highlight the differences.<br />
See how the rainbow picture is transformed:</p>
<p><figure>
  <a href="#img-5c8dad7eb80fc5afff067353a6436f65">
    <img src="/img/misc/invert-hue-roate.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-5c8dad7eb80fc5afff067353a6436f65">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/invert-hue-roate.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="possible-improvements">Possible improvements</h2>
<p>Perhaps we could avoid applying the filter globally, or at least avoid to apply it somehow to the images, so that they are not affected by those filters.<br />
At least for now, that&rsquo;s good enough for me!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Functional builder approach in Java</title><link>https://glaforge.dev/posts/2024/01/16/java-functional-builder-approach/</link><pubDate>Tue, 16 Jan 2024 08:33:32 +0100</pubDate><guid>https://glaforge.dev/posts/2024/01/16/java-functional-builder-approach/</guid><description>&lt;p>In Java, builders are a pretty classical pattern for creating complex objects with lots of attributes.
A nice aspect of builders is that they help reduce the number of constructors you need to create,
in particular when not all attributes are required to be set (or if they have default values).&lt;/p>
&lt;p>However, I&amp;rsquo;ve always found builders a bit verbose with their &lt;code>newBuilder()&lt;/code> / &lt;code>build()&lt;/code> method combos,
especially when you work with deeply nested object graphs, leading to lines of code of builders of builders of&amp;hellip;&lt;/p></description><content:encoded>
<![CDATA[<p>In Java, builders are a pretty classical pattern for creating complex objects with lots of attributes.
A nice aspect of builders is that they help reduce the number of constructors you need to create,
in particular when not all attributes are required to be set (or if they have default values).</p>
<p>However, I&rsquo;ve always found builders a bit verbose with their <code>newBuilder()</code> / <code>build()</code> method combos,
especially when you work with deeply nested object graphs, leading to lines of code of builders of builders of&hellip;</p>
<p>As I was chatting about those builders with my colleague <a href="https://www.linkedin.com/in/deleplacevalentin/">Valentin</a>,
who is a Go developer, he told me about Golang&rsquo;s functional builder approach.
It&rsquo;s not a very common implementation practice for Java builders, but it&rsquo;s worth revisiting!</p>
<h2 id="first-the-classical-builder">First, the classical builder</h2>
<p>Let&rsquo;s start with an example.
We want to create a builder for a class with a few attributes.
Not all attributes are mandatory, some may have some default values,
and we don&rsquo;t want to create as many constructors as possible combinations of attributes.</p>
<p>Let me introduce you to my <code>SomeModel</code> class:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SomeModel</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>Float<span style="color:#bbb"> </span>temperature<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0.<span style="color:#4070a0">3f</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>maxOutputTokens<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>100;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// ... possibly many other attribtues</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span><span style="color:#06287e">SomeModel</span>(String<span style="color:#bbb"> </span>modelName,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                      </span>Float<span style="color:#bbb"> </span>temperature,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                      </span>Integer<span style="color:#bbb"> </span>maxOutputTokens)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">modelName</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">temperature</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>temperature;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">maxOutputTokens</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>maxOutputTokens;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Creating tons of constructors for the various model configurations can be painful.
Furthermore, some attributes can have the same type, so from a user perspective, it&rsquo;s hard to know which value corresponds to which parameter type.
So creating a builder can reduce that toil.</p>
<p>We could write a static builder class inside <code>SomeModel</code> along the lines of:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SomeModelBuilder</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>Float<span style="color:#bbb"> </span>temperature<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0.<span style="color:#4070a0">3f</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>maxOutputTokens<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>100;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>SomeModelBuilder<span style="color:#bbb"> </span><span style="color:#06287e">modelName</span>(String<span style="color:#bbb"> </span>modelName)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">modelName</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">this</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>SomeModelBuilder<span style="color:#bbb"> </span><span style="color:#06287e">temperature</span>(Float<span style="color:#bbb"> </span>temperature)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">temperature</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>temperature;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">this</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>SomeModelBuilder<span style="color:#bbb"> </span><span style="color:#06287e">maxOutputTokens</span>(Integer<span style="color:#bbb"> </span>maxOutputTokens)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">this</span>.<span style="color:#4070a0">maxOutputTokens</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>maxOutputTokens;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">this</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>SomeModel<span style="color:#bbb"> </span><span style="color:#06287e">build</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SomeModel(modelName,<span style="color:#bbb"> </span>temperature,<span style="color:#bbb"> </span>maxOutputTokens);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Inside <code>SomeModel</code> you would add a method to instantiate a builder:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>SomeModelBuilder<span style="color:#bbb"> </span><span style="color:#06287e">newBuilder</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SomeModelBuilder();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, the user would create a model instance with the builder as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>SomeModel.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">modelName</span>(<span style="color:#4070a0">&#34;gemini&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">temperature</span>(0.<span style="color:#4070a0">2f</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">maxOutputToken</span>(300)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Not too bad.
The are some variations to this approach, like passing the builder in the class&rsquo; constructor,
using setter methods that return <code>this</code>, using or not using final fields, etc.
But they are mostly stylistic variations.</p>
<p>However, I was wondering about this idea of a functional builder&hellip;</p>
<h2 id="existing-functional-approaches-in-java">Existing functional approaches in Java</h2>
<p>I haven&rsquo;t found much litterature on this theme.
There are 2 blog posts
(<a href="https://medium.com/beingprofessional/think-functional-advanced-builder-pattern-using-lambda-284714b85ed5">here</a>
and <a href="https://www.innovect.com/advanced-builder-using-java-8-lambda">there</a>)
that suggest an approach with lambda expressions and <code>Consumer</code>s,
but I find it even more unconventional than the approach I&rsquo;m going to describe further in this article:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>SomeModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SomeModelBuilder()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">with</span>($<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>$.<span style="color:#4070a0">modelName</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Gemini&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>$.<span style="color:#4070a0">temperature</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0.<span style="color:#4070a0">4f</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>})<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">with</span>($<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>$.<span style="color:#4070a0">maxOutputTokens</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>100);<span style="color:#bbb">
</span></span></span></code></pre></div><p>You can pass one or more lambdas in chained calls.
It&rsquo;s the end-user who controls how the model is built, not the implementor, so I feel it&rsquo;s less safe.
The use of the <code>$</code> sign is a bit of a syntactical hack to avoid repeating the name of the variable corresponding to the model.
Finally, there&rsquo;s still a builder class after all, and maybe we can find a way to get rid of it.</p>
<p>Let&rsquo;s see what Go has to offer instead, and if we can get some inspiration from it!</p>
<h2 id="the-go-approach">The Go approach</h2>
<p>My colleague <a href="https://www.linkedin.com/in/deleplacevalentin/">Valentin</a> pointed me at Dave Cheney&rsquo;s
<a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis">article</a> on Go&rsquo;s functional option pattern.
There&rsquo;s also a <a href="https://www.youtube.com/watch?v=24lFtGHWxAQ">video</a> available.</p>
<p>The idea is that the class&rsquo; constructor takes function <em>options</em> as a vararg paramter,
that are able to modify the instance that&rsquo;s being built.</p>
<p>Let&rsquo;s illustrate this with the following snippet.</p>
<p>We create a <code>struct</code> that represents our model object like in our Java example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#4070a0">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">type</span> SomeModel <span style="color:#007020;font-weight:bold">struct</span> {
</span></span><span style="display:flex;"><span>    modelName <span style="color:#902000">string</span>
</span></span><span style="display:flex;"><span>    temperature <span style="color:#902000">float32</span>
</span></span><span style="display:flex;"><span>    maxOutputTokens <span style="color:#902000">int</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We define a method to construct our model, which takes a vararg of options:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">NewModel</span>(options <span style="color:#666">...</span><span style="color:#007020;font-weight:bold">func</span>(<span style="color:#666">*</span>SomeModel)) (<span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>    m <span style="color:#666">:=</span> SomeModel{<span style="color:#4070a0">&#34;&#34;</span>, <span style="color:#40a070">0.3</span>, <span style="color:#40a070">100</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">for</span> _, option <span style="color:#666">:=</span> <span style="color:#007020;font-weight:bold">range</span> options {
</span></span><span style="display:flex;"><span>        <span style="color:#06287e">option</span>(<span style="color:#666">&amp;</span>m)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> <span style="color:#666">&amp;</span>m
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Those options are actually functions that take a model object as parameter.</p>
<p>Now we can create utility methods that create such option functions,
and we pass the value for each field of the <code>struct</code> via the method parameter.
So we have a method for each structure field: model name, temperature and max output tokens:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">modelName</span>(name <span style="color:#902000">string</span>) <span style="color:#007020;font-weight:bold">func</span>(<span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> <span style="color:#007020;font-weight:bold">func</span>(m <span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>        m.modelName = name
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">temperature</span>(temp <span style="color:#902000">float32</span>) <span style="color:#007020;font-weight:bold">func</span>(<span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> <span style="color:#007020;font-weight:bold">func</span>(m <span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>        m.temperature = temp
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">maxOutputTokens</span>(max <span style="color:#902000">int</span>) <span style="color:#007020;font-weight:bold">func</span>(<span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> <span style="color:#007020;font-weight:bold">func</span>(m <span style="color:#666">*</span>SomeModel) {
</span></span><span style="display:flex;"><span>        m.maxOutputTokens = max
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Next we can create the model in the following way, by calling the utility methods that return functions
that are able to modify the <code>struct</code>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">main</span>() {
</span></span><span style="display:flex;"><span>    m <span style="color:#666">:=</span> <span style="color:#06287e">NewModel</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#06287e">modelName</span>(<span style="color:#4070a0">&#34;gemini&#34;</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#06287e">temperature</span>(<span style="color:#40a070">0.5</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#06287e">maxOutputTokens</span>(<span style="color:#40a070">100</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    fmt.<span style="color:#06287e">Println</span>(m)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Notice there&rsquo;s not even a <code>NewBuilder()</code> or <code>Build()</code> method!</p>
<h2 id="lets-implement-our-functional-builder-in-java">Let&rsquo;s implement our functional builder in Java!</h2>
<p>We can follow the same approach in Java.
Instead of Go functions, we&rsquo;ll use Java&rsquo;s lambdas.
Our lambdas will be converted into <code>Consumer</code>s of <code>SomeModel</code>.</p>
<p>So let&rsquo;s recreate our <code>SomeModel</code> class, with the same fields as before.
This time, however, the constructor won&rsquo;t be <code>private</code>, and it&rsquo;ll take a list of options
(lambda expressions that consume instances of <code>SomeModel</code>).
We&rsquo;ll iterate over all of them to execute them:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.function.Consumer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SomeModel</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>Float<span style="color:#bbb"> </span>temperature<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0.<span style="color:#4070a0">3f</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>Integer<span style="color:#bbb"> </span>maxOutputTokens<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>100;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#06287e">SomeModel</span>(ModelOption...<span style="color:#bbb"> </span>options)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(Option<span style="color:#bbb"> </span>option<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>options)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>option.<span style="color:#4070a0">accept</span>(<span style="color:#007020;font-weight:bold">this</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span></code></pre></div>
            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Update #1</span>
      </div>
      <div class="admonition-content">
        <p>A neat <a href="https://gist.github.com/edeandrea/27ee1c61f05e640fc6fa1e19b8fb756e">suggestion</a>
by Eric Deandrea on Twitter to use streams to filter the null options,
as I was not checking them with an extra <code>if</code> before calling <code>accept()</code> on the option:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Optional.<span style="color:#4070a0">ofNullable</span>(options)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>.<span style="color:#4070a0">map</span>(Stream::of)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>.<span style="color:#4070a0">orElseGet</span>(Stream::empty)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">   </span>.<span style="color:#4070a0">forEach</span>(option<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>option.<span style="color:#4070a0">accept</span>(<span style="color:#007020;font-weight:bold">this</span>))<span style="color:#bbb">
</span></span></span></code></pre></div>
      </div>
    </div><p>And what is this <code>ModelOption</code> class? This is just a synonym for a <code>Consumer&lt;SomeModel&gt;</code>
(so not strictly needed, but can help with readability).
It&rsquo;s a nested interface:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">interface</span> <span style="color:#0e84b5;font-weight:bold">ModelOption</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">extends</span><span style="color:#bbb"> </span>Consumer<span style="color:#666">&lt;</span>SomeModel<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next, we create similar utility methods that will update the model instance:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>ModelOption<span style="color:#bbb"> </span><span style="color:#06287e">modelName</span>(String<span style="color:#bbb"> </span>modelName)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">modelName</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>modelName;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>ModelOption<span style="color:#bbb"> </span><span style="color:#06287e">temperature</span>(Float<span style="color:#bbb"> </span>temperature)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">temperature</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>temperature;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span>ModelOption<span style="color:#bbb"> </span><span style="color:#06287e">maxOutputTokens</span>(Integer<span style="color:#bbb"> </span>maxOutputTokens)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">maxOutputTokens</span><span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>maxOutputTokens;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Now, if we want to create a model, we&rsquo;ll be able to call the constructor as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">fn.builder.SomeModel</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import static</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">fn.builder.SomeModel.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#60a0b0;font-style:italic">//...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>SomeModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>SomeModel(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>modelName(<span style="color:#4070a0">&#34;gemini&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>temperature(0.<span style="color:#4070a0">5f</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>maxOutputTokens(100)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span></code></pre></div><p>Don&rsquo;t forget to use a <code>static import</code> to keep the syntax short.</p>
<h2 id="discussion">Discussion</h2>
<p>A few advantages I see with this approach:</p>
<ul>
<li>I like the fact we&rsquo;re using a constructor to construct our model instances!</li>
<li>And the constructor is super simple and short!</li>
<li>It also means that the constructor won&rsquo;t change when there&rsquo;s going to be a new parameter to handle (better backward compatibility).
On the other hand, with a traditional builder, the constructor could also take the builder itself as sole parameter.</li>
<li>I&rsquo;m also happy that I got rid of the verbose <code>newBuilder()</code> / <code>build()</code> combo.
It feels like we don&rsquo;t really have a builder at play here.</li>
<li>At first, I was wondering if I was opening the Pandora box, as I feared developers could provide their own lambda
and potentially wreck havoc in my instance construction, but because of visibility rules,
only my methods can modify the internals of the model class</li>
<li>Although we&rsquo;re using a constructor, the fact of passing those method calls as parameters, it feels a bit like having
<a href="https://www.groovy-lang.org/objectorientation.html#_named_parameters">named arguments</a>
like in languages like Python or Groovy (which can also
<a href="https://www.groovy-lang.org/metaprogramming.html#xform-Builder">create builders for you</a> via AST transformations).
It also looks more like the classical builder too, which has that readability aspect.</li>
<li>I can pass the arguments in whichever order I want.</li>
<li>I can put validation rules both in each mutator method and in the constructor after all mutators have been called.</li>
</ul>
<p>Potential tweaks:</p>
<ul>
<li>I used non-final fields, because I wanted to be able to define my default values for some fields at definition time
rather than in the constructor, but we could certainly tweak this implementation a bit if needed.
And anyway, only my mutator methods can alter those fields, so I guess it&rsquo;s fine.</li>
<li>I was curious if I could use Java <code>enum</code>s for storing only my allowed mutators,
but I haven&rsquo;t found an effective and concise way of implementing this.
Java <code>enum</code>s don&rsquo;t work like Rust&rsquo;s, but there&rsquo;s an interesting article about this
<a href="https://www.reddit.com/r/java/comments/135i37c/rust_like_enums_in_java/">here</a> on how to implement sum types.</li>
<li>I wondered also about a mix of <code>sealed</code> <code>interface</code>s and maybe <code>record</code>s, but similarly to <code>enum</code>s,
I couldn&rsquo;t find a nice and short syntax that I was happy with.</li>
</ul>
<p>In the cons:</p>
<ul>
<li>It&rsquo;s a bit unconventional, as I haven&rsquo;t seen this approach implemented in the wild.
So maybe the approach suffers in terms of readability.</li>
<li>The other concerns I have is with discoverability.
When auto-completing code, an IDE like IntelliJ is smart enough to suggest the mutators methods can be used inside the constructor.
But it&rsquo;s not that clear that such mutator methods exist.
It&rsquo;s going to be important to document the constructor to say that those mutators exist.</li>
</ul>
<h2 id="feedback">Feedback</h2>
<p>I&rsquo;d be curious to hear your thoughts on this.
Don&rsquo;t hesitate to interact with me on
<a href="https://uwyn.net/@glaforge/111766219413506355">Mastodon</a>,
<a href="https://twitter.com/glaforge/status/1747272263546905026">Twitter</a>, or
<a href="https://bsky.app/profile/glaforge.bsky.social/post/3kj47jxwseg2m">BlueSky</a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>URL slug or how to remove accents from strings in Java</title><link>https://glaforge.dev/posts/2024/01/08/url-slug-or-how-to-remove-accents-in-java/</link><pubDate>Mon, 08 Jan 2024 10:02:47 +0100</pubDate><guid>https://glaforge.dev/posts/2024/01/08/url-slug-or-how-to-remove-accents-in-java/</guid><description>&lt;p>In this article, we&amp;rsquo;ll figure out how to create &lt;em>slugs&lt;/em>.
Not the slobbery kind of little gastropods that crawls on the ground. Instead,
we&amp;rsquo;ll see how to create the short hyphened text you can see in the URL of your web browser,
and that is often a &lt;strong>URL-friendly variation of the title of the article&lt;/strong>.&lt;/p>
&lt;blockquote>
&lt;p>Interestingly, one of the most popular posts on my blog is an almost 20 year old article that explains
how to &lt;a href="https://glaforge.dev/posts/2005/04/27/how-to-remove-accents-from-a-string/">remove accents from a string&lt;/a>.
And indeed, in &lt;em>slugs&lt;/em> you would like to remove accents, among other things.&lt;/p></description><content:encoded>
<![CDATA[<p>In this article, we&rsquo;ll figure out how to create <em>slugs</em>.
Not the slobbery kind of little gastropods that crawls on the ground. Instead,
we&rsquo;ll see how to create the short hyphened text you can see in the URL of your web browser,
and that is often a <strong>URL-friendly variation of the title of the article</strong>.</p>
<blockquote>
<p>Interestingly, one of the most popular posts on my blog is an almost 20 year old article that explains
how to <a href="https://glaforge.dev/posts/2005/04/27/how-to-remove-accents-from-a-string/">remove accents from a string</a>.
And indeed, in <em>slugs</em> you would like to remove accents, among other things.</p></blockquote>
<p>So what problem are we trying to solve today?
Let&rsquo;s say you have an article whose title is <em>&ldquo;L&rsquo;été, où est tu ?&rdquo;</em>
(which translates to: <em>&ldquo;Summer, where have you been?&rdquo;</em>&quot;).
You want your blog to have a friendly URL that looks like the title,
but without the punctuation, or the accents (also called diacritical marks),
and you also want to replace spaces with hyphens.
The final URL should then be <code>https://myblog.com/l-ete-ou-est-tu</code>.</p>
<p>A naive approach would be to try to replace all the letters with diacritical marks with their non marked equivalents.
So don&rsquo;t try to replace <em>&ldquo;é&rdquo;</em> with &ldquo;e&rdquo;, etc. You&rsquo;ll likely miss some letters in some languages.
A better approach is to take advantage of <strong>Unicode normalization</strong>.</p>
<p>If you are interested, you can learn more about
<a href="https://unicode.org/reports/tr15/images/UAX15-NormFig4.jpg">unicode normalization</a>
on the <a href="https://unicode.org/reports/tr15/">Unicode.org</a> website.
But in a nutshell, some letters, like accented letters, are a combination of a base letter, and a diacritical mark.</p>
<p>Let&rsquo;s have a look at this image from the link above:</p>
<p><figure>
  <a href="#img-e1eecc79769f255b65984b5a8cc5c0b3">
    <img src="https://unicode.org/reports/tr15/images/UAX15-NormFig3.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e1eecc79769f255b65984b5a8cc5c0b3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://unicode.org/reports/tr15/images/UAX15-NormFig3.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The uppercase <a href="https://www.compart.com/en/unicode/U+212B">angström</a> letter is comprised of
the <em>A uppercase letter</em>, and the <em>ring above</em> diacritical mark.
The composed letter has a unicode value of <code>U+212B</code> but can be decomposed
into <code>U+0041</code> <em>(uppercase A)</em> and <code>U+30A</code> <em>(ring above)</em>.</p>
<p>I&rsquo;ll spare you from the details of the various normalization forms.
But Java allows you to work with the normalized forms of letters thanks to the <code>java.text.Normalizer</code> class.
We&rsquo;ll also take advantage of Java&rsquo;s regex <code>Pattern</code> class to identify particular classes of characters.</p>
<blockquote>
<p>Be sure to check the Javadocs of the <code>Normalizer</code> and <code>Pattern</code> classes:</p>
<ul>
<li><a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/text/Normalizer.html">Normalizer</a></li>
<li><a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/regex/Pattern.html">Pattern</a></li>
</ul>
<p>The former explains how to do string normalization, and the latter will give you the list of available character classes.</p></blockquote>
<p>Let&rsquo;s have a look at the following Java snippet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.text.Normalizer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>title<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;L&#39;été, où es tu ?&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Normalizer.<span style="color:#4070a0">normalize</span>(title,<span style="color:#bbb"> </span>Normalizer.<span style="color:#4070a0">Form</span>.<span style="color:#4070a0">NFD</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">toLowerCase</span>()<span style="color:#bbb">                  </span><span style="color:#60a0b0;font-style:italic">// &#34;l&#39;été, où es tu ?&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\p{IsM}+&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span>)<span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// &#34;l&#39;ete, ou es tu ?&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">		  </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\p{IsP}+&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34; &#34;</span>)<span style="color:#bbb">   </span><span style="color:#60a0b0;font-style:italic">// &#34;l ete  ou es tu  &#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">trim</span>()<span style="color:#bbb">                         </span><span style="color:#60a0b0;font-style:italic">// &#34;l ete  ou es tu&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>.<span style="color:#4070a0">replaceAll</span>(<span style="color:#4070a0">&#34;\\s+&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;-&#34;</span>)<span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic">// &#34;l-ete-ou-es-tu&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>My approach is usually the following:</p>
<ul>
<li>First, I normalize the text into the <code>NFD</code> form <em>(canonical decomposition)</em>, so base characters and diacritical marks are now separated,</li>
<li>Then, I&rsquo;m replacing all the uppercase letters with lowercase ones,</li>
<li>Next, we use the <code>IsM</code> property which selects the the diacritical marks, and we remove them</li>
<li>Simiarly, we look at the characters which are punctuation, with the <code>IsP</code> binary property, and replace them with spaces</li>
<li>I usually trim the string at that point, as I don&rsquo;t want to have spaces at the beginning or end of the strings (when a punctuation mark is replace with a space in the previous step)</li>
<li>Eventually, all the space characters are replaced with hyphens.</li>
</ul>
<h2 id="slugify">Slugify</h2>
<p>Recently, I came across a Java library that takes care of creating slugs: <a href="https://github.com/slugify/slugify">Slugify</a>!</p>
<p>With Slugify, you can do a similar transformation as mine, with the following code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.github.slugify.Slugify</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Slugify<span style="color:#bbb"> </span>slugify<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Slugify.<span style="color:#4070a0">builder</span>().<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>slugify.<span style="color:#4070a0">slugify</span>(<span style="color:#4070a0">&#34;L&#39;été, où es tu ?&#34;</span>)<span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic">// &#34;lete-ou-es-tu&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>A small difference here is that the quote is removed, which leads to having the string <code>lete</code> instead of <code>l-ete</code>.
I find that a bit less readable at a glance, but Slugify has various knobs you can tweak to customize its output.</p>
<p>There&rsquo;s one particular thing I like about this library, it&rsquo;s its use of the
<a href="https://unicode-org.github.io/icu/userguide/icu4j/">ICU4J</a> library, which supports <strong>transliteration</strong>
<em>(<a href="https://icu.unicode.org/">ICU</a> is a well known set of libraries for full unicode and globalization support.)</em></p>
<p>The problem with our examples above is that they work well for language with latin-like alphabets.
But my examples keep characters like ideograms intact, and Slugify removes them by default.
If you want to have URLs that stay within the ASCII realm,
you can use <a href="https://en.wikipedia.org/wiki/Transliteration">transliteration</a>,
which can map text in one language into readable latin-like text that sounds like the original text.</p>
<p>So if I wanted to transliterate my string into ascii-friendly text, I could use Slugify&rsquo;s integration of ICU:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.github.slugify.Slugify</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Slugify<span style="color:#bbb"> </span>slugify<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Slugify.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">transliterator</span>(<span style="color:#007020;font-weight:bold">true</span>)<span style="color:#bbb">          </span><span style="color:#60a0b0;font-style:italic">// use transliteration</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">locale</span>(Locale.<span style="color:#4070a0">ENGLISH</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>slugify.<span style="color:#4070a0">slugify</span>(<span style="color:#4070a0">&#34;夏よ、どこにいるの？&#34;</span>)<span style="color:#bbb">  </span><span style="color:#60a0b0;font-style:italic">// &#34;xiayo-dokoniiruno&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Gemini Function Calling</title><link>https://glaforge.dev/posts/2023/12/22/gemini-function-calling/</link><pubDate>Fri, 22 Dec 2023 18:03:43 +0100</pubDate><guid>https://glaforge.dev/posts/2023/12/22/gemini-function-calling/</guid><description>&lt;p>A promising feature of the Gemini large language model released recently by &lt;a href="https://deepmind.google/">Google DeepMind&lt;/a>,
is the support for &lt;a href="https://ai.google.dev/docs/function_calling">function calls&lt;/a>.
It&amp;rsquo;s a way to supplement the model, by letting it know an external functions or APIs can be called.
So you&amp;rsquo;re not limited by the knowledge cut-off of the model: instead, in the flow of the conversation with the model,
you can pass a list of functions the model will know are available to get the information it needs,
to complete the generation of its answer.&lt;/p></description><content:encoded>
<![CDATA[<p>A promising feature of the Gemini large language model released recently by <a href="https://deepmind.google/">Google DeepMind</a>,
is the support for <a href="https://ai.google.dev/docs/function_calling">function calls</a>.
It&rsquo;s a way to supplement the model, by letting it know an external functions or APIs can be called.
So you&rsquo;re not limited by the knowledge cut-off of the model: instead, in the flow of the conversation with the model,
you can pass a list of functions the model will know are available to get the information it needs,
to complete the generation of its answer.</p>
<p>For example, if you want to ask the model about the weather, it doesn&rsquo;t have the realtime information about the weather forecast.
But we can tell it that there&rsquo;s a function that can be called, to get the forecast for a given location.
Internally, the model will acknowledge it doesn&rsquo;t know the answer about the weather,
but it will request that you call an external function that you describe, using a specific set of parameters which correspond to the user&rsquo;s request.</p>
<p>Just days ago, I wrote about how to <a href="https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/">get started with Gemini in Java</a>.
In that article, we explored how to use the hand-written Java SDK that is available to interact with Gemini from Java.
However, the Java SDK doesn&rsquo;t yet expose all the features of the model: in particular, function calling is missing.
But not all hope is lost! Because under the hood, the SDK relies on the generated protobuf classes library, which exposes everything!</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Soon, Gemini will be supported by <a href="https://github.com/langchain4j/langchain4j">LangChain4j</a>,
and the Java SDK will also provide an easier way to take care of function calling.
But in this article, I wanted to explore the use of the internal protobuf classes, to see how to best implement its support in the SDK.</p>
      </div>
    </div><p>Let&rsquo;s go step by step!</p>
<p>Instead of using the <code>GenerativeModel</code> API from the SDK, we&rsquo;ll go straight with the <code>PredictionServiceClient</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(VertexAI<span style="color:#bbb"> </span>vertexAI<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>VertexAI(projectId,<span style="color:#bbb"> </span>location))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>PredictionServiceClient<span style="color:#bbb"> </span>client<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>vertexAI.<span style="color:#4070a0">getPredictionServiceClient</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We need to prepare a function declaration to describe the kind of functions that the LLM can ask us to call, and we&rsquo;ll wrap it in a <code>Tool</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>FunctionDeclaration<span style="color:#bbb"> </span>functionDeclaration<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>FunctionDeclaration.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setName</span>(<span style="color:#4070a0">&#34;getCurrentWeather&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#4070a0">&#34;Get the current weather in a given location&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setParameters</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">OBJECT</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">putProperties</span>(<span style="color:#4070a0">&#34;location&#34;</span>,<span style="color:#bbb"> </span>Schema.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setType</span>(Type.<span style="color:#4070a0">STRING</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setDescription</span>(<span style="color:#4070a0">&#34;location&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">addRequired</span>(<span style="color:#4070a0">&#34;location&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Tool<span style="color:#bbb"> </span>tool<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Tool.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addFunctionDeclarations</span>(functionDeclaration)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Functions are described using classes that represent a subset of the OpenAPI 3 specification.</p>

    <div class="admonition important">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>
        <span>Important</span>
      </div>
      <div class="admonition-content">
        <p>This is important to provide descriptions for the functions and its parameters,
as the LLM will use that information to figure out which function to call, and which parameters should be passed.</p>
      </div>
    </div><p>Next, let&rsquo;s prepare a question asking about the weather in Paris, and configuring the text generation request with that prompt and the tool defined above:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#bbb"> </span>resourceName<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>String.<span style="color:#4070a0">format</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#4070a0">&#34;projects/%s/locations/%s/publishers/google/models/%s&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>vertexAI.<span style="color:#4070a0">getProjectId</span>(),<span style="color:#bbb"> </span>vertexAI.<span style="color:#4070a0">getLocation</span>(),<span style="color:#bbb"> </span>modelName);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Content<span style="color:#bbb"> </span>questionContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ContentMaker.<span style="color:#4070a0">fromString</span>(<span style="color:#4070a0">&#34;What&#39;s the weather in Paris?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>GenerateContentRequest<span style="color:#bbb"> </span>questionContentRequest<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentRequest.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setEndpoint</span>(resourceName)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setModel</span>(resourceName)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">addTools</span>(tool)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">addContents</span>(questionContent)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>ResponseStream<span style="color:#666">&lt;</span>GenerateContentResponse<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>responseStream<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ResponseStream<span style="color:#666">&lt;&gt;</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ResponseStreamIteratorWithHistory<span style="color:#666">&lt;&gt;</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>client<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">streamGenerateContentCallable</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">call</span>(questionContentRequest)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">iterator</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>GenerateContentResponse<span style="color:#bbb"> </span>generateContentResponse<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>responseStream.<span style="color:#4070a0">stream</span>().<span style="color:#4070a0">findFirst</span>().<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Content<span style="color:#bbb"> </span>callResponseContent<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>generateContentResponse.<span style="color:#4070a0">getCandidates</span>(0).<span style="color:#4070a0">getContent</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>If you print the <code>callResponseContent</code> variable, you&rsquo;ll see that it contains a function call request,
suggesting that you should call the predefined function with the parameter of <code>Paris</code>:</p>
<pre tabindex="0"><code>role: &#34;model&#34;
parts {
  function_call {
    name: &#34;getCurrentWeather&#34;
    args {
      fields {
        key: &#34;location&#34;
        value {
          string_value: &#34;Paris&#34;
        }
      }
    }
  }
}
</code></pre><p>At that point, as the developer, it&rsquo;s your turn to work a little, and make the call to that function yourself!
Let&rsquo;s pretend I called an external Web Service that gives weather information, and that it returns some JSON payload that would look like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;weather&#34;</span>: <span style="color:#4070a0">&#34;sunny&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;location&#34;</span>: <span style="color:#4070a0">&#34;Paris&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We need now to create a function response structure to pass that information back to the LLM:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Content<span style="color:#bbb"> </span>contentFnResp<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Content.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addParts</span>(Part.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">setFunctionResponse</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>FunctionResponse.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">setResponse</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>Struct.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">putFields</span>(<span style="color:#4070a0">&#34;weather&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>Value.<span style="color:#4070a0">newBuilder</span>().<span style="color:#4070a0">setStringValue</span>(<span style="color:#4070a0">&#34;sunny&#34;</span>).<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">putFields</span>(<span style="color:#4070a0">&#34;location&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                            </span>Value.<span style="color:#4070a0">newBuilder</span>().<span style="color:#4070a0">setStringValue</span>(<span style="color:#4070a0">&#34;Paris&#34;</span>).<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">build</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, since LLMs are actually stateless beasts, we need to give it the whole context of the conversation again,
passing the query, the function call response the model suggested us to make, as well as the response we got from the external weather service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>GenerateContentRequest<span style="color:#bbb"> </span>generateContentRequest<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>GenerateContentRequest.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setEndpoint</span>(resourceName)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setModel</span>(resourceName)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addContents</span>(questionContent)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addContents</span>(callResponseContent)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addContents</span>(contentFnResp)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addTools</span>(tool)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>And to finish, we&rsquo;ll invoke the <code>client</code> one last time with that whole dialog and information, and print a response out:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>responseStream<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ResponseStream<span style="color:#666">&lt;&gt;</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ResponseStreamIteratorWithHistory<span style="color:#666">&lt;&gt;</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>client<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">streamGenerateContentCallable</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">call</span>(generateContentRequest)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>.<span style="color:#4070a0">iterator</span>())<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(GenerateContentResponse<span style="color:#bbb"> </span>resp<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>responseStream)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(ResponseHandler.<span style="color:#4070a0">getText</span>(resp));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And happily, Gemini will reply to us that:</p>
<pre tabindex="0"><code>The weather in Paris is sunny.
</code></pre><p>What a lovely way to start the holiday season with a nice and sunny weather!</p>
<p>I wish you all happy year end festivities, and I look forward to seeing you next year.
Hopefully next month, I&rsquo;ll be able to show you some cool new SDK features or the LangChain4j integration!
Thanks for reading.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Visualize and Inspect Workflows Executions</title><link>https://glaforge.dev/posts/2023/12/22/visualize-and-inspect-workflows-executions/</link><pubDate>Fri, 22 Dec 2023 12:02:42 +0100</pubDate><guid>https://glaforge.dev/posts/2023/12/22/visualize-and-inspect-workflows-executions/</guid><description>&lt;p>When using a service like Google Cloud &lt;a href="https://cloud.google.com/workflows/">Workflows&lt;/a>,
in particular as your workflows get bigger, it can be difficult to understand what&amp;rsquo;s going on under the hood.
With multiple branches, step jumps, iterations, and also parallel branches and iterations,
if your workflow fails during an execution, until now, you had to check the execution status,
or go deep through the logs to find more details about the failed step.&lt;/p>
&lt;p>I have good news for you!
Workflows recently added some deeper introspection capability:
you can now &lt;a href="https://cloud.google.com/workflows/docs/debug-steps">view the history of execution steps&lt;/a>.
From the Google Cloud console, you can see the lists of steps, and see the logical flow between them.
The usual workflow visualisation will also highlight in green the successful steps, and in red the failed one.
Of course, it is also possible to make a curl call to get the JSON of the
&lt;a href="https://cloud.google.com/workflows/docs/debug-steps#list-entries">list of executed steps&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>When using a service like Google Cloud <a href="https://cloud.google.com/workflows/">Workflows</a>,
in particular as your workflows get bigger, it can be difficult to understand what&rsquo;s going on under the hood.
With multiple branches, step jumps, iterations, and also parallel branches and iterations,
if your workflow fails during an execution, until now, you had to check the execution status,
or go deep through the logs to find more details about the failed step.</p>
<p>I have good news for you!
Workflows recently added some deeper introspection capability:
you can now <a href="https://cloud.google.com/workflows/docs/debug-steps">view the history of execution steps</a>.
From the Google Cloud console, you can see the lists of steps, and see the logical flow between them.
The usual workflow visualisation will also highlight in green the successful steps, and in red the failed one.
Of course, it is also possible to make a curl call to get the JSON of the
<a href="https://cloud.google.com/workflows/docs/debug-steps#list-entries">list of executed steps</a>.</p>
<p>Let&rsquo;s have a look!</p>
<p>In the console, when you click on an execution, in the <code>summary</code> tab,
you&rsquo;ll see not only the failed step, but also the nice workflow graph colored green and red:</p>
<p><figure>
  <a href="#img-30d115c7a052d147848b62eccc201eb7">
    <img src="/img/workflows-days/step-visu-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-30d115c7a052d147848b62eccc201eb7">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/step-visu-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>That way, you know which path the execution followed, in a visual manner.
But you can also see the actual list of steps executed, with more details, by clicking on the <code>steps</code> tab:</p>
<p><figure>
  <a href="#img-31d2be5e47264a042a3a34ef273052fe">
    <img src="/img/workflows-days/step-visu-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-31d2be5e47264a042a3a34ef273052fe">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/step-visu-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>From this table, the filter will let you further refine particular type of steps you&rsquo;d like to investigate,
or visualise the steps of a subworkflow only, etc.</p>
<p>This is a nice improvement to the developer experience, and for your ops team,
to better understand what happens during your workflow executions!
Feel free to read more about this new capabability in the documentation about
<a href="https://cloud.google.com/workflows/docs/debug-steps">viewing the history of execution steps</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Hands on Codelabs to dabble with Large Language Models in Java</title><link>https://glaforge.dev/posts/2023/12/18/get-hands-on-codelabs-to-dabble-with-llms/</link><pubDate>Mon, 18 Dec 2023 17:31:56 +0100</pubDate><guid>https://glaforge.dev/posts/2023/12/18/get-hands-on-codelabs-to-dabble-with-llms/</guid><description>&lt;p>Hot on the heels of the &lt;a href="https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/">release of Gemini&lt;/a>,
I&amp;rsquo;d like to share a couple of resources I created to get your hands on large language models,
using &lt;a href="https://github.com/langchain4j/">LangChain4J&lt;/a>, and the &lt;a href="https://ai.google/discover/palm2/">PaLM 2&lt;/a> model.
Later on, I&amp;rsquo;ll also share with you articles and codelabs that take advantage of Gemini, of course.&lt;/p>
&lt;p>The PaLM 2 model supports 2 modes:&lt;/p>
&lt;ul>
&lt;li>text generation,&lt;/li>
&lt;li>and chat.&lt;/li>
&lt;/ul>
&lt;p>In the 2 codelabs, you&amp;rsquo;ll need to have created an account on Google Cloud, and created a project.
The codelabs will guide you through the steps to setup the environment,
and show you how to use the Google Cloud built-in shell and code editor, to develop in the cloud.&lt;/p></description><content:encoded>
<![CDATA[<p>Hot on the heels of the <a href="https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/">release of Gemini</a>,
I&rsquo;d like to share a couple of resources I created to get your hands on large language models,
using <a href="https://github.com/langchain4j/">LangChain4J</a>, and the <a href="https://ai.google/discover/palm2/">PaLM 2</a> model.
Later on, I&rsquo;ll also share with you articles and codelabs that take advantage of Gemini, of course.</p>
<p>The PaLM 2 model supports 2 modes:</p>
<ul>
<li>text generation,</li>
<li>and chat.</li>
</ul>
<p>In the 2 codelabs, you&rsquo;ll need to have created an account on Google Cloud, and created a project.
The codelabs will guide you through the steps to setup the environment,
and show you how to use the Google Cloud built-in shell and code editor, to develop in the cloud.</p>
<p>You should be a Java developer, as the examples are in Java, use the <a href="https://github.com/langchain4j/">LangChain4J</a> project, and Maven for building the code.</p>
<h3 id="generative-ai-text-generation-in-java-with-palm-and-langchain4jhttpscodelabsdevelopersgooglecomcodelabsgenai-text-gen-java-palm-langchain4jhlen0"><a href="https://codelabs.developers.google.com/codelabs/genai-text-gen-java-palm-langchain4j?hl=en#0">Generative AI text generation in Java with PaLM and LangChain4J</a></h3>
<p>In the first <a href="https://codelabs.developers.google.com/codelabs/genai-text-gen-java-palm-langchain4j?hl=en#0">codelab</a>
you can explore:</p>
<ul>
<li>how to make your first call to PaLM for simple question/answer scenarios</li>
<li>how to extract structured data out of unstructured text</li>
<li>how to use prompts and prompt templates</li>
<li>how to classify text, with an example on sentiment analysis</li>
</ul>
<h3 id="generative-ai-powered-chat-with-users-and-docs-in-java-with-palm-and-langchain4jhttpscodelabsdevelopersgooglecomcodelabsgenai-chat-java-palm-langchain4jhlen0"><a href="https://codelabs.developers.google.com/codelabs/genai-chat-java-palm-langchain4j?hl=en#0">Generative AI powered chat with users and docs in Java with PaLM and LangChain4J</a></h3>
<p>In the second <a href="https://codelabs.developers.google.com/codelabs/genai-chat-java-palm-langchain4j?hl=en#0">codelab</a>
you&rsquo;ll use the chat model to learn:</p>
<ul>
<li>how to create your first chat with the PaLM model</li>
<li>how to give your chatbot a personality, with an example with a chess player</li>
<li>how to extract structured data out of unstructured text using LangChain4J&rsquo;s AiServices and its annotations</li>
<li>how to implement Retrieval Augmented Generation (RAG) to answer questions about your own documentation</li>
</ul>
<h2 id="going-further-with-generative-ai">Going further with Generative AI</h2>
<p>If you&rsquo;re interested in going further with Generative AI, and learn more,
feel free to <a href="https://goo.gle/generativeai">join the Google Cloud Innovators program</a>.</p>
<p>Google Cloud Innovators is <strong>free</strong> and includes:</p>
<ul>
<li>live discussions, AMAs, and roadmap sessions to learn the latest directly from Googlers,</li>
<li>the latest Google Cloud news right in your inbox,</li>
<li>digital badge and video conference background,</li>
<li>and more.</li>
</ul>
<p>Go check what the <a href="https://cloud.google.com/innovators?hl=en">program offers</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Get Started with Gemini in Java</title><link>https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/</link><pubDate>Wed, 13 Dec 2023 16:45:51 +0100</pubDate><guid>https://glaforge.dev/posts/2023/12/13/get-started-with-gemini-in-java/</guid><description>&lt;p>Google announced today the availability of
&lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/gemini-support-on-vertex-ai">Gemini&lt;/a>,
its latest and more powerful Large Language Model.
Gemini is &lt;strong>multimodal&lt;/strong>, which means it&amp;rsquo;s able to consume not only text, but also images or videos.&lt;/p>
&lt;p>I had the pleasure of working on the Java samples and help with the Java SDK, with wonderful engineer colleagues, and I&amp;rsquo;d like to share some examples of &lt;strong>what you can do with Gemini, using Java&lt;/strong>!&lt;/p>
&lt;p>First of all, you&amp;rsquo;ll need to have an account on Google Cloud and created a project.
The Vertex AI API should be enabled, to be able to access the Generative AI services,
and in particular the Gemini large language model.
Be sure to check out the
&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/quickstart-multimodal?hl=en">instructions&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>Google announced today the availability of
<a href="https://cloud.google.com/blog/products/ai-machine-learning/gemini-support-on-vertex-ai">Gemini</a>,
its latest and more powerful Large Language Model.
Gemini is <strong>multimodal</strong>, which means it&rsquo;s able to consume not only text, but also images or videos.</p>
<p>I had the pleasure of working on the Java samples and help with the Java SDK, with wonderful engineer colleagues, and I&rsquo;d like to share some examples of <strong>what you can do with Gemini, using Java</strong>!</p>
<p>First of all, you&rsquo;ll need to have an account on Google Cloud and created a project.
The Vertex AI API should be enabled, to be able to access the Generative AI services,
and in particular the Gemini large language model.
Be sure to check out the
<a href="https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/quickstart-multimodal?hl=en">instructions</a>.</p>
<h2 id="preparing-your-project-build">Preparing your project build</h2>
<p>To get started with some coding, you&rsquo;ll need to create a Gradle or a Maven build file
that requires the Google Cloud libraries BOM, and the <code>google-cloud-vertexai</code> library.
Here&rsquo;s an example with Maven:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependencyManagement&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependencies&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>libraries-bom<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;scope&gt;</span>import<span style="color:#062873;font-weight:bold">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;type&gt;</span>pom<span style="color:#062873;font-weight:bold">&lt;/type&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>26.29.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependencies&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependencyManagement&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependencies&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>google-cloud-vertexai<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>    ...
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependencies&gt;</span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><h2 id="your-first-queries">Your first queries</h2>
<p>Now let&rsquo;s have a look at our first multimodal example, mixing text prompts and images:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(VertexAI<span style="color:#bbb"> </span>vertexAI<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>VertexAI(projectId,<span style="color:#bbb"> </span>location))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">byte</span><span style="color:#666">[]</span><span style="color:#bbb"> </span>imageBytes<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Base64.<span style="color:#4070a0">getDecoder</span>().<span style="color:#4070a0">decode</span>(dataImageBase64);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerativeModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GenerativeModel(<span style="color:#4070a0">&#34;gemini-pro-vision&#34;</span>,<span style="color:#bbb"> </span>vertexAI);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentResponse<span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>model.<span style="color:#4070a0">generateContent</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ContentMaker.<span style="color:#4070a0">fromMultiModalData</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;What is this image about?&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>PartMaker.<span style="color:#4070a0">fromMimeTypeAndData</span>(<span style="color:#4070a0">&#34;image/jpg&#34;</span>,<span style="color:#bbb"> </span>imageBytes)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(ResponseHandler.<span style="color:#4070a0">getText</span>(response));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You instantiate <code>VertexAI</code> with your Google Cloud project ID, and the region location of your choice.
To pass images to Gemini, you should either pass the bytes directly,
or you can pass a URI of an image stored in a cloud storage bucket (like <code>gs://my-bucket/my-img.jpg</code>).
You create an instance of the model. Here, I&rsquo;m using <code>gemini-pro-vision</code>.
But later on, a <code>gemini-ultra-vision</code> model will also be available.
Let&rsquo;s ask the model to generate content with the <code>generateContent()</code> method,
by passing both a text prompt, and also an image.
The <code>ContentMaker</code> and <code>PartMaker</code> classes are helpers to further simplify
the creation of more advanced prompts that mix different modalities.
But you could also just pass a simple string as argument of the <code>generateContent()</code> method.
The <code>ResponseHandler</code> utility will retrieve all the text of the answer of the model.</p>
<p>Instead of getting the whole output once all the text is generated,
you can also adopt a streaming approach:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>model.<span style="color:#4070a0">generateContentStream</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">stream</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">forEach</span>(System.<span style="color:#4070a0">out</span>::print);<span style="color:#bbb">
</span></span></span></code></pre></div><p>You can also iterate over the stream with a <code>for</code> loop:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>ResponseStream<span style="color:#666">&lt;</span>GenerateContentResponse<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>responseStream<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>model.<span style="color:#4070a0">generateContentStream</span>(<span style="color:#4070a0">&#34;Why is the sky blue?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(GenerateContentResponse<span style="color:#bbb"> </span>responsePart:<span style="color:#bbb"> </span>responseStream)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">print</span>(ResponseHandler.<span style="color:#4070a0">getText</span>(responsePart));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="lets-chat">Let&rsquo;s chat!</h2>
<p>Gemini is a multimodal model, and it&rsquo;s actually both a text generation model, but also a chat model.
So you can chat with Gemini, and ask a series of questions in context.
There&rsquo;s a handy <code>ChatSession</code> utility class which simplifies the handling of the conversation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(VertexAI<span style="color:#bbb"> </span>vertexAI<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>VertexAI(projectId,<span style="color:#bbb"> </span>location))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerateContentResponse<span style="color:#bbb"> </span>response;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GenerativeModel<span style="color:#bbb"> </span>model<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>GenerativeModel(modelName,<span style="color:#bbb"> </span>vertexAI);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ChatSession<span style="color:#bbb"> </span>chatSession<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ChatSession(model);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatSession.<span style="color:#4070a0">sendMessage</span>(<span style="color:#4070a0">&#34;Hello.&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(ResponseHandler.<span style="color:#4070a0">getText</span>(response));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatSession.<span style="color:#4070a0">sendMessage</span>(<span style="color:#4070a0">&#34;What are all the colors in a rainbow?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(ResponseHandler.<span style="color:#4070a0">getText</span>(response));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>chatSession.<span style="color:#4070a0">sendMessage</span>(<span style="color:#4070a0">&#34;Why does it appear when it rains?&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(ResponseHandler.<span style="color:#4070a0">getText</span>(response));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is convenient to use <code>ChatSession</code> as it takes care of keeping track
of past questions from the user, and answers from the assistant.</p>
<h2 id="going-further">Going further</h2>
<p>This is just a few examples of the capabilities of Gemini. Be sure to check out some of the
<a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/vertexai/snippets/src/main/java/vertexai/gemini">samples that are available on Github</a>.
Read <a href="https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/quickstart-multimodal?hl=en">more about Gemini and Generative AI</a> in the Google Cloud documentation.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Generative AI in practice: Concrete LLM use cases in Java, with the PaLM API</title><link>https://glaforge.dev/talks/2023/11/13/gen-ai-with-palm-2-and-java/</link><pubDate>Mon, 13 Nov 2023 15:36:51 +0100</pubDate><guid>https://glaforge.dev/talks/2023/11/13/gen-ai-with-palm-2-and-java/</guid><description>&lt;p>Large Language Models, available through easy to use APIs, bring powerful machine learning tools in the hands of developers.
Although Python is usually seen as the &lt;em>lingua franca&lt;/em> of everything ML, with LLM APIs and LLM orchestration frameworks, complex tasks become easier to implement for enterprise developers.&lt;/p>
&lt;h2 id="abstract">Abstract&lt;/h2>
&lt;blockquote>
&lt;p>Large language models (LLMs) are a powerful new technology that can be used for a variety of tasks, including generating text, translating languages, and writing different kinds of creative content. However, LLMs can be difficult to use, especially for developers who are not proficient in Python, the lingua franca for AI. So what about us Java developers? How can we make use of Generative AI?&lt;/p></description><content:encoded>
<![CDATA[<p>Large Language Models, available through easy to use APIs, bring powerful machine learning tools in the hands of developers.
Although Python is usually seen as the <em>lingua franca</em> of everything ML, with LLM APIs and LLM orchestration frameworks, complex tasks become easier to implement for enterprise developers.</p>
<h2 id="abstract">Abstract</h2>
<blockquote>
<p>Large language models (LLMs) are a powerful new technology that can be used for a variety of tasks, including generating text, translating languages, and writing different kinds of creative content. However, LLMs can be difficult to use, especially for developers who are not proficient in Python, the lingua franca for AI. So what about us Java developers? How can we make use of Generative AI?</p>
<p>This presentation will go through how to use LLMs in Java without the need for Python. We will use the PaLM API, provided by Google Cloud’s Vertex AI services, to perform a variety of tasks, such as searching through documentation, generating kids stories, summarizing content, extracting keywords or entities, and more.
In our journey through demos, we&rsquo;ll discover LangChain4J, a wonderful LLM orchetratore for Java developers that simplifies the implementation of advanced LLM use cases.</p></blockquote>
<p>I had the chance to get this talk recorded at Devoxx Belgium:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/ioTPfL9cd9k?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And you can check the slides here:</p>
<script async class="speakerdeck-embed" data-id="be0c44ac898f4ce5b905a8389bb751e2" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tech Watch #5 — November, 15, 2023</title><link>https://glaforge.dev/posts/2023/11/13/tech-watch-0005/</link><pubDate>Mon, 13 Nov 2023 12:33:50 +0100</pubDate><guid>https://glaforge.dev/posts/2023/11/13/tech-watch-0005/</guid><description>&lt;ul>
&lt;li>
&lt;p>Some friends shared this article from Uwe Friedrichsen, tilted &lt;a href="https://www.ufried.com/blog/back_to_the_future/">back to the future&lt;/a>, that talks about this feeling of &amp;ldquo;déjà-vu&amp;rdquo;, this impression that in IT we keep on reinventing the wheel. With references to mainframes, Uwe compared CICS to Lambda function scheduling, JCL to step functions, mainframe software development environments to the trendy platform engineering. There are two things I like about this article. First of all, it rings a bell with me, as we&amp;rsquo;ve seen the pendulum swing as we keep reinventing some patterns or rediscovering certain best practices, sometimes favoring an approach one day, and coming back to another approach the next day. But secondly, Uwe referenced Gunter Dueck who talked about spirals rather than a pendulum. I&amp;rsquo;ve had that same analogy in mind for years: rather than swinging on one side to the next and back, I always had this impression that we&amp;rsquo;re circling and spiraling, but each time, even when passing on the same side, we&amp;rsquo;ve learned something along the way, and we&amp;rsquo;re getting closer to an optimum, with a slightly different view angle, and hopefully with a better view and more modern practices. Last week at FooConf #2 in Helsinki, I was just talking with my friend &lt;a href="https://agiledeveloper.com/aboutus.html">Venkat Subramaniam&lt;/a> about this spiral visualisation, and I&amp;rsquo;m glad to see I&amp;rsquo;m not the only one thinking that IT is spiraling rather than swinging like a pendulum.&lt;/p></description><content:encoded>
<![CDATA[<ul>
<li>
<p>Some friends shared this article from Uwe Friedrichsen, tilted <a href="https://www.ufried.com/blog/back_to_the_future/">back to the future</a>, that talks about this feeling of &ldquo;déjà-vu&rdquo;, this impression that in IT we keep on reinventing the wheel. With references to mainframes, Uwe compared CICS to Lambda function scheduling, JCL to step functions, mainframe software development environments to the trendy platform engineering. There are two things I like about this article. First of all, it rings a bell with me, as we&rsquo;ve seen the pendulum swing as we keep reinventing some patterns or rediscovering certain best practices, sometimes favoring an approach one day, and coming back to another approach the next day. But secondly, Uwe referenced Gunter Dueck who talked about spirals rather than a pendulum. I&rsquo;ve had that same analogy in mind for years: rather than swinging on one side to the next and back, I always had this impression that we&rsquo;re circling and spiraling, but each time, even when passing on the same side, we&rsquo;ve learned something along the way, and we&rsquo;re getting closer to an optimum, with a slightly different view angle, and hopefully with a better view and more modern practices. Last week at FooConf #2 in Helsinki, I was just talking with my friend <a href="https://agiledeveloper.com/aboutus.html">Venkat Subramaniam</a> about this spiral visualisation, and I&rsquo;m glad to see I&rsquo;m not the only one thinking that IT is spiraling rather than swinging like a pendulum.</p>
</li>
<li>
<p><a href="https://automerge.org/blog/2023/11/06/automerge-repo/">Automerge-repo, a batteries included toolkit for building local-first applications</a><br />
<a href="https://automerge.org/">Automerge</a> is one of the most well-known CRDT algorithm (Conflict-Free Replicated Data Type) that allows you to implement collaborative applications (think Google Docs kind of collaboration, for example). With CRDT algorithms and data structures, concurrent changes on different devices can be merged automatically without requiring a central server, and without complex merge processes. However, having an algorithm and data structure is one thing, but to put the whole system in place is not necessarily easy. This new automerge-repo projects tries to solve this problem, by offering networking and storage adapters to facilitate the communication between the peers, or with a potential sync server.</p>
</li>
<li>
<p>The WebAssembly Garbage Collection proposal (WasmGC) lands in the latest Chrome version. The <a href="https://v8.dev/blog/wasm-gc-porting">V8 team dives into the details about WasmGC</a>. It&rsquo;ll be particularly useful to better support garbage collected languages (like Java and friends) without having to ship a garbage collector in each wasm package.</p>
</li>
<li>
<p>Although I&rsquo;m not developing native apps for Macs, I spotted this article about an <a href="https://gregoryszorc.com/blog/2022/08/08/achieving-a-completely-open-source-implementation-of-apple-code-signing-and-notarization/">open source implementation of Apple code signing and notarization</a>, implemented in Rust, and that can run on non-Mac hardware. With this approach, when you&rsquo;re building native apps for the Mac, you can integrate that approach in your Linux-based CI/CD pipeline, without having a Mac box somewhere.</p>
</li>
<li>
<p><a href="https://medium.com/google-cloud/langchain-chain-types-large-document-summarization-using-langchain-and-google-cloud-vertex-ai-1650801899f6">Document summarization is an area where large language models excel</a>. There are different approaches to do so when your context window can&rsquo;t fit the whole document to summarize. In this article, different approaches are mentioned: stuffing (when it fits in the context window), Map/Reduce to split the content in sections that can be summarised and a summary of summary can be made, and the more sequential Refine method where we summarize what fits in memory, and then ask to refine that first summary with the details of the following sections, till we run out of content.</p>
</li>
<li>
<p>Large Language Models face two big issues: one is hallucinations and how to mitigate them by grounding answers or finding ways to assess the response&rsquo;s factuality, and the other one is prompt injection, as a malignant attacker can misguide an LLM to do something else than what it was programmed for. The folks at Scott Logic developed a demo based on the idea of ImmersiveLabs&rsquo; <a href="https://prompting.ai.immersivelabs.com/">online playground</a> to experiment with prompt injection and techniques to circumvent them. There&rsquo;s also an <a href="https://blog.scottlogic.com/2023/11/03/spy-logic.html">article</a> that talks about the project, and a <a href="https://blog.scottlogic.com/2023/10/31/mitigating-prompt-injections.md.html">video</a> that shows it all in action.</p>
</li>
<li>
<p>My good friend Ken Kousen dives into <a href="https://kousenit.org/2023/11/06/the-magic-of-ai-services-with-langchain4j/">the magic of AI Services with LangChain4J</a>. He has a nice blog post, and also a great accompanying <a href="https://www.youtube.com/watch?v=Bx2OpE1nj34">video</a> on YouTube where he shows some of the powerful features of LangChain4J, in particular the AI service that allows you to decorate an interface with annotations to interact with your large language model and get plain Java types or objects in return.</p>
</li>
<li>
<p>My colleague Romin Irani also <a href="https://medium.com/google-cloud/integrating-langchain4j-and-palm-2-chat-bison-model-a684cefd67af">integrated LangChain4J and the PaLM 2 chat model</a>, showing how to deploy a Google Cloud Function chatbot.</p>
</li>
<li>
<p>Baeldung also gives in <a href="https://www.baeldung.com/java-langchain-basics">introduction to LangChain4J</a> showing the basics of prompts, models, memory management, retrieval, chains, and agents.</p>
</li>
<li>
<p><a href="https://www.linkedin.com/pulse/langchain4j-using-redis-stephan-janssen-lobpe/">LangChain4J using Redis</a>: Stephan Janssen, the founder of Devoxx, is using <a href="https://github.com/langchain4j">LangChain4J</a> inside the Devoxx CFP and schedule application. In this article on LinkedIn, he explains how he used Redis to store vector embeddings corresponding to the talks of the conference, to search for similar talks.</p>
</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tech Watch #4 — October, 27, 2023</title><link>https://glaforge.dev/posts/2023/10/27/tech-watch-004/</link><pubDate>Fri, 27 Oct 2023 14:06:59 +0200</pubDate><guid>https://glaforge.dev/posts/2023/10/27/tech-watch-004/</guid><description>&lt;ul>
&lt;li>
&lt;p>The &lt;a href="https://www.stateof.ai/">State of AI report&lt;/a> is pretty interesting to read (even if long!). Among the major sections: research, industry, but also politics, safety, and some predictions. You&amp;rsquo;ll find an executive summary in one slide, on slide #8. &lt;br />
&lt;br />
On #22, &lt;strong>emergent capabilities of LLMs&lt;/strong> is covered and mentions Stanford&amp;rsquo;s research that talks about the importance of more linear and continuous measures as otherwise capabilities sound like they emerge out of the blue. &lt;br />
&lt;br />
On #23, they talk about the &lt;strong>context length of LLMs being the new parameter count&lt;/strong>, as models try to have bigger context windows. &lt;br />
&lt;br />
However, on slide #24, they also talk about researchers who showed that &lt;strong>in long context windows the content provided in the middle is more ignored&lt;/strong> by LLMs compared to content at the beginning or end of the window. &lt;br />
So be sure to &lt;strong>put the important bits first or last&lt;/strong>, but not lost in the middle. &lt;br />
&lt;br />
Slide #26 speaks about &lt;strong>smaller models trained with smaller curated datasets and can rival 50x bigger models&lt;/strong>. &lt;br />
&lt;br />
Slide #28 wonders if we&amp;rsquo;re &lt;strong>running out of human-generated data&lt;/strong>, and thus, if we&amp;rsquo;re going to have our LLMs trained on&amp;hellip; LLM generated data!&lt;/p></description><content:encoded>
<![CDATA[<ul>
<li>
<p>The <a href="https://www.stateof.ai/">State of AI report</a> is pretty interesting to read (even if long!). Among the major sections: research, industry, but also politics, safety, and some predictions. You&rsquo;ll find an executive summary in one slide, on slide #8. <br />
<br />
On #22, <strong>emergent capabilities of LLMs</strong> is covered and mentions Stanford&rsquo;s research that talks about the importance of more linear and continuous measures as otherwise capabilities sound like they emerge out of the blue. <br />
<br />
On #23, they talk about the <strong>context length of LLMs being the new parameter count</strong>, as models try to have bigger context windows. <br />
<br />
However, on slide #24, they also talk about researchers who showed that <strong>in long context windows the content provided in the middle is more ignored</strong> by LLMs compared to content at the beginning or end of the window. <br />
So be sure to <strong>put the important bits first or last</strong>, but not lost in the middle. <br />
<br />
Slide #26 speaks about <strong>smaller models trained with smaller curated datasets and can rival 50x bigger models</strong>. <br />
<br />
Slide #28 wonders if we&rsquo;re <strong>running out of human-generated data</strong>, and thus, if we&rsquo;re going to have our LLMs trained on&hellip; LLM generated data!</p>
</li>
<li>
<p><a href="https://projector.tensorflow.org/">3D visualisation of vector embeddings from Tensorflow</a> <br />
As I&rsquo;m working on a small application that would help visuliase vector embeddings, I was looking for existing apps or articles that show how vectors can be similar, and thus their semantic to be similar as well. And I came across this existing visualisation from the Tensorflow project, which uses the Word2Vec embedding approach. I like the fact you can use different 3D projections techniques like t-SNE or PCA, and you see related vectors closer in the 3D space, as their meaning is closer too.</p>
</li>
<li>
<p><a href="https://www.citusdata.com/blog/2023/10/26/making-postgres-tick-new-features-in-pg-cron/">A cron extension for PostgreSQL</a><br />
pg_cron is an extension for the PostgreSQL database that adds scheduling capabilities. It can even be scheduled to run your procedures or other SQL queries every few seconds.</p>
</li>
<li>
<p><a href="https://protomaps.com/">Protomaps</a> is a free and open source map of the world, deployable as a single static file on cloud storage (including Google Cloud Storage). You can use OpenStreetMap tiles, as it&rsquo;s distributed with a version of OSM. It&rsquo;s using an efficient and open archive format for pyramids of tile data, accessible via HTTP Range requests.</p>
</li>
<li>
<p><a href="https://artistassistapp.com/">ArtistAssistApp</a> is an application which can tell you which oil or water color paints to use and mix to create similar looking colors for your painting, as you try to reproduce a photo. As a wannabe painter myself, I always struggle creating mixes that match real colors, and this tool is pretty clever to let you find the right mix (at least if you use some well-known paint brands). This also reminds me of <a href="https://scrtwpns.com/mixbox/">mixbox</a> which simulates color mixing as real color pigments mix in real paint, and using such algorithm would greatly improve the real-life accuracy of color mixes in digital art painting applications.</p>
</li>
<li>
<p><a href="https://vectorizer.ai/">Vectorizer</a> is an online tool to transform an image into an SVG file. As I&rsquo;m playing a bit with Generative AI-based image generation, sometimes, the upscalers don&rsquo;t suffice, and you want to transform a nice generated image into a vectorial format (for example clipart-like illustrations), so they scale gracefully in slide decks or on websites.</p>
</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tech Watch #3 — October, 20, 2023</title><link>https://glaforge.dev/posts/2023/10/20/tech-watch-003/</link><pubDate>Fri, 20 Oct 2023 17:32:51 +0200</pubDate><guid>https://glaforge.dev/posts/2023/10/20/tech-watch-003/</guid><description>&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://horstmann.com/unblog/2023-10-03/index.html">Stop Using char in Java. And Code Points&lt;/a>&lt;br />
It&amp;rsquo;s a can of worms, when you start messing with chars, code points, and you&amp;rsquo;re likely going to get it wrong in the end. As much as possible, stay away from chars and code points, and instead, use as much as possible the String methods like &lt;code>indexOf()&lt;/code> / &lt;code>substring()&lt;/code>, and some regex when you really need to find grapheme clusters.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Paul King shared his presentations on &lt;a href="https://speakerdeck.com/paulk/groovy-today">Why use Groovy in 2023&lt;/a> and an
&lt;a href="https://speakerdeck.com/paulk/groovy-roadmap">update on the Groovy 5 roadmap&lt;/a>It&amp;rsquo;s interesting to see how and where Groovy goes beyond what is offered by Java, sometimes thanks to its dynamic nature, sometimes because of its compile-time transformation capabilities. When Groovy adopts the latest Java features, there&amp;rsquo;s always a twist to make things even groovier in Groovy!&lt;/p></description><content:encoded>
<![CDATA[<ul>
<li>
<p><a href="https://horstmann.com/unblog/2023-10-03/index.html">Stop Using char in Java. And Code Points</a><br />
It&rsquo;s a can of worms, when you start messing with chars, code points, and you&rsquo;re likely going to get it wrong in the end. As much as possible, stay away from chars and code points, and instead, use as much as possible the String methods like <code>indexOf()</code> / <code>substring()</code>, and some regex when you really need to find grapheme clusters.</p>
</li>
<li>
<p>Paul King shared his presentations on <a href="https://speakerdeck.com/paulk/groovy-today">Why use Groovy in 2023</a> and an
<a href="https://speakerdeck.com/paulk/groovy-roadmap">update on the Groovy 5 roadmap</a>It&rsquo;s interesting to see how and where Groovy goes beyond what is offered by Java, sometimes thanks to its dynamic nature, sometimes because of its compile-time transformation capabilities. When Groovy adopts the latest Java features, there&rsquo;s always a twist to make things even groovier in Groovy!</p>
</li>
<li>
<p><a href="https://blog.scottlogic.com/2023/10/18/the-state-of-webassembly-2023.html">The State of WebAssembly in 2023</a><br />
I often enjoy the articles from the folks at Scott Logic. This one is about a survey they ran on the topic of WebAssembly. Languages like Rust and JavaScript are seeing increased usage (for targeting wasm). Wasm is used a lot for web app development, but serverless seems to be he second most common use case, as well as for hosting plugin environments. The survey also mentions that threads, garbage collection and the new component model are the features developer are most interested in. For WASI, all the I/O related proposals like HTTP, filesystem support, sockets, are the ones developers want (although WASIX which covered this area received mixed reactions).</p>
</li>
<li>
<p><a href="https://arstechnica.com/information-technology/2023/09/telling-ai-model-to-take-a-deep-breath-causes-math-scores-to-soar-in-study/">Tell your LLM to take a deep breath!</a><br />
We tend to humanize large language models via <a href="https://en.wikipedia.org/wiki/Anthropomorphism">anthropomorphism</a>, as much as we see human faces in anything like with <a href="https://en.wikipedia.org/wiki/Pareidolia">pareildolia</a>, although LLMs are neither sentients nor human. So it&rsquo;s pretty ironic that to get a better result in some logic problem solving, we need to tell the LLM to actually take a deep breath! Are they now able to breathe?</p>
</li>
<li>
<p><a href="https://hackerone.com/reports/2199174">Wannabe security researcher asks Bard for vulnerabilities in cURL</a><br />
Large Language Models can be super creative, that&rsquo;s why we employ them to imagine new stories, create narratives, etc. And it seems wannabe security experts believe that what LLMs say is pure facts, probably what happened to this person that reported that they asked Bard to find a vulnerability in cURL! And Bard indeed managed to be creative enough to craft an hypothetical exploit, even explaining where a possible integer overflow could take place. Unfortunately, the generated exploit text contained many errors (wrong method signature, invented changelog, code that doesn&rsquo;t compile, etc.)</p>
</li>
<li>
<p><a href="https://www.beren.io/2023-03-19-LLMs-confabulate-not-hallucinate/">LLMs confabulate, they don&rsquo;t hallucinate</a><br />
A few times, I&rsquo;ve seen this mention on social networks about the fact we should say that LLM confabulate, instead of hallucinate. Confabulation is usually a brain disorder that makes people confidently tell things that may be true or not, in a convincing fashion (they don&rsquo;t even know it&rsquo;s false or a lie). Hallucination is more of a misinterpretation of the sensory input, like having the impression to see a pink elephant! The article linked above explains the rationale.</p>
</li>
<li>
<p>Greg Kamradt tweets about the <a href="https://twitter.com/GregKamradt/status/1711772496159252981">use cases for multimodal vision+text LLMs</a><br />
You&rsquo;d think that you could just get a model that describes a picture as a text, and then mix that description with other text snippets. But models that really fully understand both images and texts are way more powerful than this. In this tweet, Greg distinguishes different scenarios: description, interpretation, recommendation, convertion, extraction, assistance and evaluatation. For example, we could imagine transforming an architecture diagram into a proper Terraform YAML file, or a UI mockup into a snippet of code that builds that UI for real. You You could show a picture of a dish, and ask for its recipe!</p>
</li>
<li>
<p><a href="https://blog.jetbrains.com/blog/2023/10/16/ai-graphics-at-jetbrains-story/">The Story of AI Graphics at JetBrains</a><br />
I&rsquo;ve always loved generative and procedural art, both for games and indeed for art. I really enjoyed this article which is going through the story of how they are generating their nice splash screens and animations for the JetBrains family of products. Neural networks at play here!</p>
</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tech Watch #2 — Oct 06, 2023</title><link>https://glaforge.dev/posts/2023/10/06/tech-watch-002/</link><pubDate>Fri, 06 Oct 2023 16:34:35 +0200</pubDate><guid>https://glaforge.dev/posts/2023/10/06/tech-watch-002/</guid><description>&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://ig.ft.com/generative-ai/">Generative AI exists because of the transformer&lt;br />
&lt;/a>I confess I rarely read the Financial Times, but they have a really neat articles with animations on how large language models work, thanks to the transformer neural network architecture, an architecture invented by Google in 2017. They talk about text vector embeddings, how the self-attention makes LLM understand the relationship between words and the surrounding context, and also doesn&amp;rsquo;t forget to mention hallucinations, how &amp;ldquo;grounding&amp;rdquo; and RLHF (Reinforcement Learning with Human Feedback) can help mitigate them to some extent.&lt;/p></description><content:encoded>
<![CDATA[<ul>
<li>
<p><a href="https://ig.ft.com/generative-ai/">Generative AI exists because of the transformer<br />
</a>I confess I rarely read the Financial Times, but they have a really neat articles with animations on how large language models work, thanks to the transformer neural network architecture, an architecture invented by Google in 2017. They talk about text vector embeddings, how the self-attention makes LLM understand the relationship between words and the surrounding context, and also doesn&rsquo;t forget to mention hallucinations, how &ldquo;grounding&rdquo; and RLHF (Reinforcement Learning with Human Feedback) can help mitigate them to some extent.</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=ioTPfL9cd9k&amp;t=7s">Generative AI in practice: Concrete LLM use cases in Java, with the PaLM API</a> (video)<br />
At Devoxx Belgium this week, the biggest theme of the conference was Generative AI and Large Language Models. The audience being mainly Java-focused, there was a very strong interest for Java developers to be able to take advantage of GenAI / LLMs in Java, instead of the ubiquitous Python. And all sessions along those lines were fully packed. The conference featured Microsoft&rsquo;s Java SemanticKernel, the open source LangChain4J project, or Spring&rsquo;s AI experimental module. The link above is the video of my presentation I did on using PaLM API but for the Java developer, using different approaches, and also with <a href="https://github.com/langchain4j/langchain4j">LangChain4J</a>.</p>
</li>
<li>
<p><a href="https://medium.com/google-cloud/what-ai-assistant-for-a-developer-is-all-about-723de644a449">What &ldquo;AI-Assistant for a Developer&rdquo; is all about?</a> and <a href="https://seroter.com/2023/09/28/an-ai-assisted-cloud-its-a-thing-now-and-here-are-six-ways-its-already-made-my-cloud-experience-better/">An AI-assisted cloud? It&rsquo;s a thing now, and here are six ways it&rsquo;s already made my cloud experience better</a> are two articles from my colleagues Romin and Richard about how AI assistants will progressively make us, developers, more productive and stay in the flow.</p>
</li>
<li>
<p><a href="https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/">Heredoc notation in Dockerfiles<br />
</a>Did you know you can use the &ldquo;heredoc&rdquo; notation in Dockerfiles, like &laquo;EOF some script commands EOF, to run multiple commands like in a Bash file? It&rsquo;s just like writing RUN commands separated with &amp;&amp;, but in a nice script, and without creating extra layers. I think I&rsquo;ll start using this in my next Dockerfiles!</p>
</li>
<li>
<p><a href="https://tonsky.me/blog/unicode/">The Absolute Minimum Every Software Developer Must Know About Unicode in 2023<br />
</a>UTF-8 is pretty much ubiquitous nowadays, so we don&rsquo;t need to think about which encoding is used, but rather think about how to use UTF-8 properly. Unicode assigns a character (actually a code point) a number. Unicode is big, with more than 1 million code points. UTF-8 is an encoding: a way to store code points in memory, but the article mentions the other UTF encodings as well. But unicode is tricky as a characters with a diacritical mark, an emoji, etc, can be graphemes: the combination of several code points. And there are lots of tricky corner cases that stem from that (like the length of a string isn&rsquo;t trivial to calculate). A particular character (like a vowel with an accent) can be look alike another, but their composition of code points might differ, hence why there are different compositions and decompositions. So it&rsquo;s important to normalize strings when doing comparisons.</p>
</li>
<li>
<p><a href="https://github.com/jqlang/jq/releases">Jq released version 1.7<br />
</a>There&rsquo;s a new release of jq, the super handy command-line tool to explore and massage your JSON payloads! The new version uses decimal number literals to preserve precision. A new pick(stream) method to do projections (to filter just the stuff you&rsquo;re interested in), a debug() method to log some messages through stderr, and and abs() function, and plenty other little refinements and bug fixes.</p>
</li>
<li>
<p><a href="https://buildkite.com/blog/goodbye-integers-hello-uuids">Goodbye integers. Hello UUIDv7!<br />
</a>BuildKite&rsquo;s article delves into their choice of UUIDv7 for primary keys. The initially started with a combination of sequential primary keys for efficient indexing, but with a UUID as a secondary key for external use. But the characteristics of the 7th version of the UUID specification leads them to just use UUIDv7 instead. UUID eliminate the issue of coordination in distributed systems. Their random aspect makes them less guessable. But the drawback can be poor database index locality.</p>
</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Client-side consumption of a rate-limited API in Java</title><link>https://glaforge.dev/posts/2023/10/02/client-side-consumption-of-a-rate-limited-api-in-java/</link><pubDate>Mon, 02 Oct 2023 22:48:39 +0200</pubDate><guid>https://glaforge.dev/posts/2023/10/02/client-side-consumption-of-a-rate-limited-api-in-java/</guid><description>&lt;p>In the literature, you&amp;rsquo;ll easily find information on how to rate-limit your API. I even talked about
&lt;a href="https://speakerdeck.com/glaforge/the-never-ending-rest-api-design-debate-devoxx-france-2016?slide=80">Web API rate limitation&lt;/a>
years ago at a conference, covering the usage of
&lt;a href="https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-07.html">HTTP headers like X-RateLimit-*&lt;/a>.&lt;/p>
&lt;p>Rate limiting is important to help your service cope with too much load, or also to implement a tiered pricing scheme
(the more you pay, the more requests you&amp;rsquo;re allowed to make in a certain amount of time). There are useful libraries like
&lt;a href="https://micronaut-projects.github.io/micronaut-ratelimiter/snapshot/guide/index.html">Resilience4j&lt;/a>
that you can configure for Micronaut web controllers, or &lt;a href="https://www.baeldung.com/spring-bucket4j">Bucket4j&lt;/a>
for your Spring controllers.&lt;/p></description><content:encoded>
<![CDATA[<p>In the literature, you&rsquo;ll easily find information on how to rate-limit your API. I even talked about
<a href="https://speakerdeck.com/glaforge/the-never-ending-rest-api-design-debate-devoxx-france-2016?slide=80">Web API rate limitation</a>
years ago at a conference, covering the usage of
<a href="https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-07.html">HTTP headers like X-RateLimit-*</a>.</p>
<p>Rate limiting is important to help your service cope with too much load, or also to implement a tiered pricing scheme
(the more you pay, the more requests you&rsquo;re allowed to make in a certain amount of time). There are useful libraries like
<a href="https://micronaut-projects.github.io/micronaut-ratelimiter/snapshot/guide/index.html">Resilience4j</a>
that you can configure for Micronaut web controllers, or <a href="https://www.baeldung.com/spring-bucket4j">Bucket4j</a>
for your Spring controllers.</p>
<p>Oddly, what is harder to find is information about how to consume APIs that are rate-limited.
Although there are usually way more consumers of rate-limited APIs, than producers of such APIs!</p>
<p>Today, I&rsquo;d like to talk about this topic: how to consume APIs that are rate-limited.
And since I&rsquo;m a Java developer, I&rsquo;ll focus on Java-based solutions.</p>
<p>The use case which led me to talk about this topic (on Twitter / X in particular,
with a <a href="https://twitter.com/glaforge/status/1705933799635268050">fruitful conversation</a> with my followers)
is actually about a Java API which is an SDK for a Web API that is rate-limited.
I&rsquo;ll briefly cover consuming Web APIs, but will focus more on using the Java API instead.</p>
<h2 id="consuming-web-apis">Consuming Web APIs</h2>
<h3 id="rate-limit-headers">Rate limit headers</h3>
<p>Let&rsquo;s say we are calling a Web API that is rate-limited to 60 requests per minute.
We could call it 60 times in a second without hitting the limit then wait for a minute, or call it once every second.
Usually, a sliding time window is used to check that within that minute, no more than 60 requests have been made.</p>
<p>If the rate-limited API is well behaved and provides X-RateLimit headers, you can check what those headers say.
Taking the explanations from the IETF <a href="https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/">draft</a>:</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Rate limit headers</span>
      </div>
      <div class="admonition-content">
        <ul>
<li><strong>RateLimit-Limit</strong>: containing the requests quota in the time window;</li>
<li><strong>RateLimit-Remaining</strong>: containing the remaining requests quota in the current window;</li>
<li><strong>RateLimit-Reset</strong>: containing the time remaining in the current window, specified in seconds.</li>
</ul>
      </div>
    </div><p>Note that the draft mentions RateLimit-* as headers, but often in the wild, I&rsquo;ve seen those headers always prefixed with &ldquo;X-&rdquo; instead.
And sometimes, some APIs add a hyphen between Rate and Limit! So it&rsquo;s hard to create a general consumer class that could deal with
<a href="https://stackoverflow.com/questions/16022624/examples-of-http-api-rate-limiting-http-response-headers">all cases</a>.</p>
<p>Those headers inform you about the quota, how much is left, and when the quota should be back to its full capacity (if you don&rsquo;t consume more requests).
So you could certainly stage your requests accordingly &mdash; we will talk about how to schedule your requests in Java in the second section.</p>
<p>Another thing to keep in mind is that the quota may be shared among API consumers.
Maybe you have several parallel threads that will call the API and consume the API quota.
So when you see the reset header, maybe the API will have been called by another thread already, leaving you with a smaller amount of requests left in the quota.</p>
<h3 id="exponential-backoff-and-jitter">Exponential backoff and jitter</h3>
<p>The API that triggered my research actually doesn&rsquo;t provide any rate limitation headers.
So another approach is needed. A classical approach is to use an exponential backoff.
It was nicely <a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/">documented</a> on the AWS blog, a while ago.</p>
<p>The idea is that when you face an over quota error, you&rsquo;re going to retry the call after, for example, one second.
And if you&rsquo;re getting another error, you&rsquo;ll wait a little longer, by multiplying the interval by a constant, like doubling.
So at first, on the first error, you wait 1 second before retrying, next 2 seconds, then 4 seconds, etc.
You can use a fractional multiplier, of course.</p>
<p>But as explained in the article, if all clients fail at the same time, they will retry roughly at the same time as well, after one, two, four seconds.
So the idea is to add some randomness, the jitter, to more evenly spread out the retries, to avoid having new bursts of traffic at roughly the same moments.</p>
<p>There&rsquo;s another good article on Baeldung about
<a href="https://www.baeldung.com/resilience4j-backoff-jitter">exponential backoff and jitter using Resilience4J</a> for your API consumers.</p>
<h2 id="consuming-a-java-api">Consuming a Java API</h2>
<p>Back to my use case, the underlying Web API I&rsquo;m using doesn&rsquo;t feature rate limitation headers.
And since there&rsquo;s a Java library that wraps that API anyway, I&rsquo;d rather just use that Java API for convenience.
When a rate limit is hit, the API will throw an exception.
So I can catch that exception, and deal with it, maybe applying the same exponential backoff + jitter strategy.</p>
<p>However, I know the rate limit of the API.
So instead of eagerly calling the API as fast as possible, getting an exception, waiting a bit, and trying again&hellip;
I&rsquo;d rather just call the API at the pace I&rsquo;m allowed to use it.</p>
<p>Let&rsquo;s say I have a hypothetical API that takes a String as argument and returns a String:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">class</span>  <span style="color:#0e84b5;font-weight:bold">RateLimitedApi</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">call</span>(String<span style="color:#bbb"> </span>arg)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>arg.<span style="color:#4070a0">toUpperCase</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="sleeping-a-bit">Sleeping a bit&hellip;</h3>
<p>A first, but naive, idea would be to just add some pause after each call:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(<span style="color:#902000">int</span><span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>0;<span style="color:#bbb"> </span>i<span style="color:#bbb"> </span><span style="color:#666">&lt;</span><span style="color:#bbb"> </span>20;<span style="color:#bbb"> </span>i<span style="color:#666">++</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>api.<span style="color:#4070a0">call</span>(<span style="color:#4070a0">&#34;abc&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Thread.<span style="color:#4070a0">sleep</span>(100);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And instead of making the same call with the same argument, you could iterate over an array or list:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(String<span style="color:#bbb"> </span>s<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>api.<span style="color:#4070a0">call</span>(s);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Thread.<span style="color:#4070a0">sleep</span>(100);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Well, it works, but the API call takes some time as well, so you may have to adjust the sleep time accordingly, so it&rsquo;s not really ideal.
The call could also be longer than the actual wait time really needed between two invocations.</p>
<h3 id="scheduled-execution">Scheduled execution</h3>
<p>A better approach would be to use Java&rsquo;s scheduled executors, with a few threads, in case of long API execution times that overlap.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>scheduler<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Executors.<span style="color:#4070a0">newScheduledThreadPool</span>(4))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>scheduledCalls<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>scheduler.<span style="color:#4070a0">scheduleAtFixedRate</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>api.<span style="color:#4070a0">call</span>(<span style="color:#4070a0">&#34;abc&#34;</span>),<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>0,<span style="color:#bbb"> </span>100,<span style="color:#bbb"> </span>TimeUnit.<span style="color:#4070a0">MILLISECONDS</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Instead of calling the API with the same argument, how would you call it for a series of different values,
but then stop the scheduler once we&rsquo;re done with all the values?
You could take advantage of some kind of queue (here a ConcurrentLinkedDeque) to pop the arguments one at a time.
Once you&rsquo;ve cleared all the elements of the queue, you shut down the scheduler altogether.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>args<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>ConcurrentLinkedDeque<span style="color:#666">&lt;&gt;</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;a&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;b&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;c&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;d&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;e&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;f&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;g&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;h&#34;</span>,<span style="color:#bbb"> </span>...<span style="color:#4070a0">&#34;x&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;y&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;z&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>scheduler<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Executors.<span style="color:#4070a0">newScheduledThreadPool</span>(4))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>scheduler.<span style="color:#4070a0">scheduleAtFixedRate</span>(()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(<span style="color:#666">!</span>args.<span style="color:#4070a0">isEmpty</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>api.<span style="color:#4070a0">call</span>(args.<span style="color:#4070a0">pop</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>scheduler.<span style="color:#4070a0">shutdown</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>},<span style="color:#bbb"> </span>0,<span style="color:#bbb"> </span>100,<span style="color:#bbb"> </span>TimeUnit.<span style="color:#4070a0">MILLISECONDS</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="one-more-in-the-bucket4j">One more in the Bucket4J!</h3>
<p>In the introduction, I mentioned some great libraries like Resilience4J and Bucket4J.
Let&rsquo;s have a look at an approach using <a href="https://bucket4j.com/">Bucket4J</a>.</p>
<p>Scheduling is fine, to respect the rate limit, but you may perhaps want to get as many calls through as possible,
while still respecting the rate. So a different approach is necessary.</p>
<p>Bucket4J is based on the <a href="https://en.wikipedia.org/wiki/Token_bucket">token bucket algorithm</a>.
It offers a very rich and fine-grained set of rate limit definitions, if you want to allow bursts, or prefer a regular flow (like our schedule earlier).
Be sure to check out the <a href="https://bucket4j.com/8.4.0/toc.html#quick-start-examples">documentation</a> for the details.</p>
<p>Let&rsquo;s see how to define my limited consumption rate of 10 per second:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>args<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>List.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;a&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;b&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;c&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;d&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;e&#34;</span>,<span style="color:#bbb"> </span>...<span style="color:#4070a0">&#34;x&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;y&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;z&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>bucket<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Bucket.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">addLimit</span>(Bandwidth.<span style="color:#4070a0">simple</span>(10,<span style="color:#bbb"> </span>Duration.<span style="color:#4070a0">ofSeconds</span>(1)))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(String<span style="color:#bbb"> </span>arg<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>bucket.<span style="color:#4070a0">asBlocking</span>().<span style="color:#4070a0">consumeUninterruptibly</span>(1);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>api.<span style="color:#4070a0">call</span>(arg);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>It&rsquo;s pretty explicit: I create a limit that corresponds to a bandwidth of 10 tokens per second.
With this simple strategy, the bucket is refilled greedily: every 100ms a new token will be available again.
But it&rsquo;s also possible to configure it differently, to say that you want to allow another 10 calls once every second.</p>
<p>Then I have a simple for loop to iterate over the list of arguments I must pass to the API,
but I introduce an instruction that blocks until a token is available &mdash; ie. that I have the right to call the API again while respecting the rate limit.</p>
<p>Also beware of API calls that take a lot of time, as here we&rsquo;re using a blocking call that blocks the calling thread.
So if API calls take longer than the time a new token is available in the bucket, you&rsquo;ll end up calling the API much less frequently than the allowed rate limit.</p>
<p>However, with Bucket4J, the bucket can be used in a thread-safe manner, you can have several threads consuming from the same API,
using the same shared bucket, or you can make parallel calls with a single consumer as well, to use the quota to its maximum.</p>
<p>Let&rsquo;s use executors to parallelize our calls:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(ExecutorService<span style="color:#bbb"> </span>executor<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>Executors.<span style="color:#4070a0">newFixedThreadPool</span>(4))<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(String<span style="color:#bbb"> </span>arg<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>bucket.<span style="color:#4070a0">asBlocking</span>().<span style="color:#4070a0">consumeUninterruptibly</span>(1);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>executor.<span style="color:#4070a0">submit</span>(()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>api.<span style="color:#4070a0">call</span>(arg));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Be careful though, that doing so, your API calls are not necessarily following the exact same order as the order of the input collection.
In my case, I didn&rsquo;t care about the order of execution.</p>
<p>Last little tweak we could make since Java 21 was released recently, we could make use of virtual threads,
instead of threads! So let&rsquo;s push our example forward in the 21th century with this small change when creating our executor service:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Executors.<span style="color:#4070a0">newVirtualThreadPerTaskExecutor</span>()<span style="color:#bbb">
</span></span></span></code></pre></div><p>So far, we have only called the API without taking care of the returned result.
We could update the examples above with an extra line to add the argument and result in a ConcurrentHashMap or to use the result immediately.
Or we could also explore one last solution, using CompletableFutures and/or ExecutorCompletionService.
But I&rsquo;m not 100% satisfied with what I came up with so far.
So I might update this article if I find a convenient and elegant solution later on.</p>
<p>Time to wrap up!</p>
<h2 id="summary">Summary</h2>
<p>In this article, we explored the less covered topic of consuming a rate-limited API.
First, we discussed approaches for consuming Web APIs that are well-behaved, exposing rate limitation headers,
and those less well-behaved using an exponential backoff and jitter approach.
We then moved on to the case of Java APIs, doing a simple sleep to call the API at a cadence that respects the rate limit.
We also had a look at scheduled executions.
And we finished our journey with the help of the powerful Bucket4J library.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tech Watch #1 — Sept 29, 2023</title><link>https://glaforge.dev/posts/2023/09/29/tech-watch-001/</link><pubDate>Fri, 29 Sep 2023 16:05:13 +0200</pubDate><guid>https://glaforge.dev/posts/2023/09/29/tech-watch-001/</guid><description>&lt;p>Inspired my by super boss &lt;a href="https://twitter.com/rseroter">Richard Seroter&lt;/a> with his regular &lt;a href="https://seroter.com/2023/09/28/daily-reading-list-september-28-2023-171/">daily reading list&lt;/a>, I decided to record and share my &lt;em>tech watch&lt;/em>, every week (or so).
I always take notes of interesting articles I read for my own curiosity and to remember them when I need those references later on. But also to share them with &lt;a href="https://lescastcodeurs.com/">Les Cast Codeurs podcast&lt;/a>!
So I hope it&amp;rsquo;ll be interesting to my readers too!&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.honeycomb.io/llms-demand-observability-driven-development">LLMs Demand Observability-Driven Development&lt;br />
&lt;/a>A great tribune from &lt;a href="https://twitter.com/mipsytipsy">Charity Majors&lt;/a> on the importance of observability-driven development, in the wake of large language models. Developing LLM based solutions is typically not something you can do with a classical test-driven approach, as you only really get proper test data when you have it coming from production usage. Furthermore, LLMs are pretty much unpredictable and underterministic. But with observability in place, you can better understand why there&amp;rsquo;s latency in some scenarios, why the LLM came to certain solutions, and this will help you improve as your learn along the way.&lt;/p></description><content:encoded>
<![CDATA[<p>Inspired my by super boss <a href="https://twitter.com/rseroter">Richard Seroter</a> with his regular <a href="https://seroter.com/2023/09/28/daily-reading-list-september-28-2023-171/">daily reading list</a>, I decided to record and share my <em>tech watch</em>, every week (or so).
I always take notes of interesting articles I read for my own curiosity and to remember them when I need those references later on. But also to share them with <a href="https://lescastcodeurs.com/">Les Cast Codeurs podcast</a>!
So I hope it&rsquo;ll be interesting to my readers too!</p>
<ul>
<li>
<p><a href="https://www.honeycomb.io/llms-demand-observability-driven-development">LLMs Demand Observability-Driven Development<br />
</a>A great tribune from <a href="https://twitter.com/mipsytipsy">Charity Majors</a> on the importance of observability-driven development, in the wake of large language models. Developing LLM based solutions is typically not something you can do with a classical test-driven approach, as you only really get proper test data when you have it coming from production usage. Furthermore, LLMs are pretty much unpredictable and underterministic. But with observability in place, you can better understand why there&rsquo;s latency in some scenarios, why the LLM came to certain solutions, and this will help you improve as your learn along the way.</p>
</li>
<li>
<p><a href="https://blog.langchain.dev/building-chat-langchain-2/">How LangChain rebuilt their LLM documentation chatbot<br />
</a>For example: the choice of docs to parse: indexing source code didn&rsquo;t yield great results. Citing sources lets users dive deeper into the documentation and double check the LLM didn&rsquo;t hallucinate. Quality evaluation is important, to assess at each step of the process the impact of each change, each tweaks of your prompts, each change in the docs that are ingested. Also, how do you handle reindexing the documents, when there are changes in a document, when there are new pages to be indexed, or that disappear, to keep track of what has to be updated in the stored vector embeddings in vector store. A great trick about how to rephrase questions: sometimes you ask a question, ask a refinement, but you don&rsquo;t formulate a whole new question, so you can actually ask the LLM to reformulate a full question based on the conversation context, so as to find more meaningful similar text embeddings in the vector database.</p>
</li>
<li>
<p><a href="https://macoscontainers.org/">macOS containers<br />
</a>You can run all sorts of Linux flavors inside containers, on all platforms, and even Windows containers. But with the macOS containers projects, that you can install with Homebrew, you can also install and run macOS containers. It&rsquo;s still early days for the project, it seems, and there are limitated container support in macOS itself, but it sounds promising. Also, macOS containers only run on top of macOS  itself.</p>
</li>
<li>
<p><a href="https://adriano.fyi/posts/2023-09-24-choose-postgres-queue-technology/">Using PostgreSQL for queuing<br />
</a>With our without extensions, I see a lot of articles mentioning using PostgreSQL for everything! With the pgVector extension, for example, you can use Postgres as a vector database for storing parsed documents for your LLM use cases. In this article, the author suggests taking advantage of its pub/sub (with notify/listen) and row locking capabilities to implement queuing, and thus replacing other dedicated queuing components in your architecture.</p>
</li>
<li>
<p><a href="https://stable-diffusion-art.com/controlnet-sdxl/">Use ControlNet with StableDiffusion&rsquo;s SDXL<br />
</a>You&rsquo;ve probably all seen some cool images with some subliminal text appear, or with weird squares or spirals shapes, on social networks. This tutorial explains how you can guide StableDiffusion&rsquo;s SDXL model with ControlNet to shape particular picture generations, or to create pictures in the style of other pictures.</p>
</li>
<li>
<p><a href="https://huggingface.co/docs/transformers.js/index">Transformer.js<br />
</a>A JavaScript transformer implementation that allows to load HuggingFace models, and do predictions and other LLM tasks right in the browser.</p>
</li>
<li>
<p><a href="https://github.com/jbellis/jvector/">JVector<br />
</a>An open source Java project for fast vector search, used in Astra DB&rsquo;s vector search. This project was mentioned in TheNewStack&rsquo;s <a href="https://thenewstack.io/5-hard-problems-in-vector-search-and-how-cassandra-solves-them/">article</a> on how Astra DB solves 5 typical problems of vector search. So for those who want to embed an Java vector store in their LLM use cases, this might be an option to look into, besides Lucene, for example.</p>
</li>
<li>
<p><a href="https://www.marktechpost.com/2023/09/19/llms-knowledge-graphs/">Mixing LLMs and a Knowledge Graphs<br />
</a>Inherently, knowledge graphs have structured information and relationships, that LLM based projects can take advantage of. The article discusses different approaches and patterns to bind them together, to reduce hallucination, enhance transparency &amp; interpretability.</p>
</li>
<li>
<p><a href="https://andydote.co.uk/2023/09/19/tracing-is-better/">Tracing is better than logging!<br />
</a>It&rsquo;s often hard to figure out from logs what happened when a problem occurred. It&rsquo;s slightly better with structured logging to do some querying though. But with tracing, you can see correlations between traces, as they are nested, and see all the attributes you can attach to those spans, without mentioning the fact you can more easily understand where time is spent rather than just have a point in time like with a log statement.</p>
</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Discovering LangChain4J, the Generative AI orchestration library for Java developers</title><link>https://glaforge.dev/posts/2023/09/25/discovering-langchain4j/</link><pubDate>Mon, 25 Sep 2023 19:08:04 +0200</pubDate><guid>https://glaforge.dev/posts/2023/09/25/discovering-langchain4j/</guid><description>&lt;p>As I started my journey with Generative AI and Large Language Models, I&amp;rsquo;ve been overwhelmed with the omnipresence of Python.
Tons of resources are available with Python front and center. However, I&amp;rsquo;m a Java developer
(with a penchant for &lt;a href="https://groovy-lang.org/">Apache Groovy&lt;/a>, of course).
So what is there for me to create cool new Generative AI projects?&lt;/p>
&lt;p>When I built my first experiment with the
&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/api-quickstart">PaLM API&lt;/a>,
using the integration within the Google Cloud&amp;rsquo;s Vertex AI offering,
I called the available &lt;a href="https://cloud.google.com/vertex-ai/docs/reference/rest">REST API&lt;/a>,
from my &lt;a href="https://micronaut.io/">Micronaut&lt;/a> application.
I used Micronaut&amp;rsquo;s built-in mechanism to marshal / unmarshal the REST API constructs to proper classes.
Pretty straightfoward.&lt;/p></description><content:encoded>
<![CDATA[<p>As I started my journey with Generative AI and Large Language Models, I&rsquo;ve been overwhelmed with the omnipresence of Python.
Tons of resources are available with Python front and center. However, I&rsquo;m a Java developer
(with a penchant for <a href="https://groovy-lang.org/">Apache Groovy</a>, of course).
So what is there for me to create cool new Generative AI projects?</p>
<p>When I built my first experiment with the
<a href="https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/api-quickstart">PaLM API</a>,
using the integration within the Google Cloud&rsquo;s Vertex AI offering,
I called the available <a href="https://cloud.google.com/vertex-ai/docs/reference/rest">REST API</a>,
from my <a href="https://micronaut.io/">Micronaut</a> application.
I used Micronaut&rsquo;s built-in mechanism to marshal / unmarshal the REST API constructs to proper classes.
Pretty straightfoward.</p>
<p>You can learn more about this first app in my previous articles on
<a href="https://glaforge.dev/posts/2023/06/08/creating-kids-stories-with-generative-ai/">generating kid stories</a>
and <a href="https://glaforge.dev/posts/2023/05/30/getting-started-with-the-palm-api-in-the-java-ecosystem/">how to get started with the PaLM API</a></p>
<p>But soon after, I discovered that the Vertex AI Java SDK, which covers all products and services of Vertex AI,
added support for the PaLM API thanks to a new
<a href="https://cloud.google.com/vertex-ai/docs/generative-ai/text/test-text-prompts#generative-ai-test-text-prompt-java">prediction service client</a> class.
I was happy and decided to try it! So here&rsquo;s how making a simple call to the LLM looks like from Groovy:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;com.google.cloud:google-cloud-aiplatform:3.24.0&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.cloud.aiplatform.v1beta1.*</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.protobuf.Value</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.protobuf.util.JsonFormat</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>String instance <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#39;&#39;&#39;{ &#34;prompt&#34;: &#34;Tell me more about Large Language Models&#34;}&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>String parameters <span style="color:#666">=</span> <span style="color:#4070a0">&#39;&#39;&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  &#34;temperature&#34;: 0.2,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  &#34;maxOutputTokens&#34;: 256,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  &#34;topP&#34;: 0.95,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  &#34;topK&#34;: 40
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">}&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>String project <span style="color:#666">=</span> <span style="color:#4070a0">&#34;my-llm-java-demos&#34;</span>
</span></span><span style="display:flex;"><span>String location <span style="color:#666">=</span> <span style="color:#4070a0">&#34;us-central1&#34;</span>
</span></span><span style="display:flex;"><span>String publisher <span style="color:#666">=</span> <span style="color:#4070a0">&#34;google&#34;</span>
</span></span><span style="display:flex;"><span>String model <span style="color:#666">=</span> <span style="color:#4070a0">&#34;text-bison&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> predictionServiceSettings <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>    PredictionServiceSettings<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">setEndpoint</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;${location}-aiplatform.googleapis.com:443&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> predictionServiceClient <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>    PredictionServiceClient<span style="color:#666">.</span><span style="color:#4070a0">create</span><span style="color:#666">(</span>predictionServiceSettings<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> endpointName <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>    EndpointName<span style="color:#666">.</span><span style="color:#4070a0">ofProjectLocationPublisherModelName</span><span style="color:#666">(</span>project<span style="color:#666">,</span> location<span style="color:#666">,</span> publisher<span style="color:#666">,</span> model<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> instanceValue <span style="color:#666">=</span> Value<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>JsonFormat<span style="color:#666">.</span><span style="color:#4070a0">parser</span><span style="color:#666">().</span><span style="color:#4070a0">merge</span><span style="color:#666">(</span>instance<span style="color:#666">,</span> instanceValue<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> instances <span style="color:#666">=</span> <span style="color:#666">[</span>instanceValue<span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> parameterValueBuilder <span style="color:#666">=</span> Value<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>JsonFormat<span style="color:#666">.</span><span style="color:#4070a0">parser</span><span style="color:#666">().</span><span style="color:#4070a0">merge</span><span style="color:#666">(</span>parameters<span style="color:#666">,</span> parameterValueBuilder<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> parameterValue <span style="color:#666">=</span> parameterValueBuilder<span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> resp <span style="color:#666">=</span> predictionServiceClient<span style="color:#666">.</span><span style="color:#4070a0">predict</span><span style="color:#666">(</span>endpointName<span style="color:#666">,</span> instances<span style="color:#666">,</span> parameterValue<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// resp[0].content
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>println resp<span style="color:#666">.</span><span style="color:#4070a0">predictionsList</span><span style="color:#666">.</span><span style="color:#4070a0">first</span><span style="color:#666">().</span><span style="color:#4070a0">structValue</span><span style="color:#666">.</span><span style="color:#4070a0">fieldsMap</span><span style="color:#666">[</span><span style="color:#4070a0">&#39;content&#39;</span><span style="color:#666">].</span><span style="color:#4070a0">stringValue</span> 
</span></span></code></pre></div><p>You create a PredictionServiceSettings, then an EndpointName, and a PredictionServiceClient to call its <code>predict()</code> method. Not overly complicated to set up.</p>
<p>However, there are really two things that I really dislike about this API:</p>
<ul>
<li>Why are we parsing some JSON strings and creating some Protobuf structures? This isn&rsquo;t very developer friendly to me.</li>
<li>And then, it also returns some generic Protobuf structure response, that I have to navigate through to find the relevant bits I&rsquo;m interested in, instead of letting me call something like <code>resp[0].content</code>.</li>
</ul>
<p>I&rsquo;d rather have a proper set of Java classes that represent my prompt, my LLM settings, the response, etc. I was a bit disappointed and preferred the approach I took with REST marshalling / unmarshalling in my Micronaut application &mdash; you can check the <a href="https://github.com/glaforge/bedtimestories">code on Github</a>.</p>
<p><figure>
  <a href="#img-3cc6d8fbcfa1ad8db6a5d47415311600">
    <img src="https://avatars.githubusercontent.com/u/132277850?v=4"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3cc6d8fbcfa1ad8db6a5d47415311600">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://avatars.githubusercontent.com/u/132277850?v=4"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="here-comes-the-delight-with-langchain4j">Here comes the delight, with LangChain4J!</h2>
<p>If you&rsquo;re following the Generative AI field, you&rsquo;ll have come across the <a href="https://www.langchain.com/">LangChain</a> project. It&rsquo;s a Python (and Javascript) orchestrator framework to connect various building blocks: large language models, document loaders, text splitters, output parsers, vector stores to store text embeddings, tools, and prompts.</p>
<p>With just a few lines of code, you&rsquo;re able to create some great integrations to implement your Generative AI use cases, like for example following the <a href="https://www.langchain.com/use-case/retrieval">Retrieval Augmented Generation</a> pattern to create chat bots that talk with your documentation.</p>
<p>Remember that I&rsquo;m a Java developer? I played a bit with the Python version of LangChain (I didn&rsquo;t try the Javascript/Typescript variant) but I wasn&rsquo;t at ease with Python, and I didn&rsquo;t want to learn a whole new ecosystem to implement my Generative AI ideas.</p>
<p>Fortunately, that&rsquo;s when I discovered the open source <a href="https://github.com/langchain4j">LangChain4J</a> project! This is also an AI orchestrator framework, but for Java! It&rsquo;s very much inspired by the original LangChain project, but independent. So this is the perfect match for my programming language skills and Generative AI needs.</p>
<p>Now, let&rsquo;s compare our protobuf-<em>obstruse</em> example from earlier, with an equivalent one based on LangChain4J (this time I used the chat model instead of the text model):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;dev.langchain4j:langchain4j-vertex-ai:0.22.0&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">dev.langchain4j.model.vertexai.*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>VertexAiChatModel vertexAiChatModel <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>    VertexAiChatModel<span style="color:#666">.</span><span style="color:#4070a0">builder</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">endpoint</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;us-central1-aiplatform.googleapis.com:443&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">project</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;my-llm-java-demos&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">location</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;us-central1&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">publisher</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;google&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">modelName</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;chat-bison@001&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">temperature</span><span style="color:#666">(</span><span style="color:#40a070">1.0</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">maxOutputTokens</span><span style="color:#666">(</span><span style="color:#40a070">256</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">topK</span><span style="color:#666">(</span><span style="color:#40a070">40</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">topP</span><span style="color:#666">(</span><span style="color:#40a070">0.95</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">maxRetries</span><span style="color:#666">(</span><span style="color:#40a070">3</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> response <span style="color:#666">=</span> vertexAiChatModel<span style="color:#666">.</span><span style="color:#4070a0">sendUserMessage</span><span style="color:#666">(</span>
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;What is the best Large Language Model?&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>println response<span style="color:#666">.</span><span style="color:#4070a0">text</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>It&rsquo;s very declarative and straightforward! I define my endpoint and my model settings with one builder. And then I just send messages to that chat model with just strings. And the response is also a simple string.</p>
<p>LangChain4J has won my heart!</p>
<h2 id="whats-next">What&rsquo;s next?</h2>
<p>I didn&rsquo;t stop there, I also built another Generative AI use case: I created a project that lets me ask questions about some documentation (in my case, I wanted to query the Apache Groovy documentation.) I&rsquo;ll tell you more about that project in a forthcoming article, as we dive deeper in LangChain4J, to cover text embeddings, vector stores, and more.</p>
<p>I&rsquo;ll be covering this topic on Generative AI with Java at
<a href="https://devoxx.be/talk/?id=4452">Devoxx Belgium</a> next week, and
<a href="https://devoxx.ma/talk/?id=4901">Devoxx Morocco</a> the following one.</p>
<p>But you can have a look already at some of the <a href="https://github.com/langchain4j/langchain4j-examples/tree/main/other-examples/src/main/java">more advanced examples</a>, to see how you can calculate vector embeddings locally with the all-MiniLM-L6-v2 embedding model, and store the vectors in a convenient in-memory vector store (<a href="http://all_minilm_l6_v2">link</a>), how to do text classification (<a href="https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/embedding/classification/EmbeddingModelTextClassifierExample.java">link</a>), how to talk chat with your documents with conversational retrieval chains (<a href="https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/ChatWithDocumentsExamples.java">link</a>).</p>
<p>LangChain4J is still young, but already pretty powerful, and offers integrations with VertexAI and OpenAI, with vector stores like <a href="https://www.trychroma.com/">ChromaDB</a>, <a href="https://www.pinecone.io/">Pinecone</a> or <a href="https://weaviate.io/">Weaviate</a> databases, and more.</p>
<p>Be sure to <a href="https://github.com/langchain4j">checkout LangChain4J</a> if you want to build your next Generative AI use case with Java!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Custom Environment Variables in Workflows</title><link>https://glaforge.dev/posts/2023/07/06/custom-environment-variables-in-workflows/</link><pubDate>Thu, 06 Jul 2023 14:44:24 +0200</pubDate><guid>https://glaforge.dev/posts/2023/07/06/custom-environment-variables-in-workflows/</guid><description>&lt;p>In addition to the built-in &lt;a href="https://cloud.google.com/workflows/docs/reference/environment-variables">environment variables available by default&lt;/a> in Google Cloud &lt;a href="https://cloud.google.com/workflows">Workflows&lt;/a> (like the project ID, the location, the workflow ID, etc.) it’s now possible to define your own &lt;a href="https://cloud.google.com/workflows/docs/use-environment-variables">custom environment variables&lt;/a>!&lt;/p>
&lt;p>Why is it useful and important? It’s particularly handy when you want to read information that is dependent on the deployment of your workflow, like, for example, information about the environment you’re running in. Is my workflow running in development, staging, or production environment? Then you can read your custom &lt;code>MY_ENVIRONMENT&lt;/code> variable, like you read the existing built-in environment variables. And you define such variables at deployment time.&lt;/p></description><content:encoded>
<![CDATA[<p>In addition to the built-in <a href="https://cloud.google.com/workflows/docs/reference/environment-variables">environment variables available by default</a> in Google Cloud <a href="https://cloud.google.com/workflows">Workflows</a> (like the project ID, the location, the workflow ID, etc.) it’s now possible to define your own <a href="https://cloud.google.com/workflows/docs/use-environment-variables">custom environment variables</a>!</p>
<p>Why is it useful and important? It’s particularly handy when you want to read information that is dependent on the deployment of your workflow, like, for example, information about the environment you’re running in. Is my workflow running in development, staging, or production environment? Then you can read your custom <code>MY_ENVIRONMENT</code> variable, like you read the existing built-in environment variables. And you define such variables at deployment time.</p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/workflows-patterns-and-best-practices-part-3#:~:text=Plan%20for%20multi%2Denvironment%20orchestrations">best practices articles</a>, and in the more detailed article on <a href="https://cloud.google.com/blog/topics/developers-practitioners/multi-environment-service-orchestrations">multi-environment service orchestration</a>, my colleague <a href="https://atamel.dev/">Mete</a> and I had shared ways to implement such an approach. You have a workflow that orchestrates some API calls. But you want to have one single workflow definition that can run in different environments. There were a few approaches, each with pros and cons, to do that: by passing the API URLs as parameters of the workflow execution (but it doesn’t work for event-triggered workflows), by replacing some special text tokens before deployment, or even with some string replacement when deploying with Terraform.</p>
<p>But now, things are simpler! Let’s see how to <strong>define custom environment variables</strong> and how to <strong>access them</strong>.</p>
<h2 id="calling-an-api-endpoint-in-staging-or-in-prod">Calling an API endpoint in staging or in prod</h2>
<p>With custom environment variables, you can now deploy the exact same workflow definition, but with different variables, using the <code>sys.get_env()</code> built-in function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>- call_a_service:
</span></span><span style="display:flex;"><span>    call: http.get
</span></span><span style="display:flex;"><span>    args:
</span></span><span style="display:flex;"><span>      url: <span style="color:#70a0d0">${</span><span style="color:#bb60d5">sys</span>.get_env(<span style="color:#4070a0">&#34;SERVICE_URL&#34;</span><span style="color:#70a0d0">}</span>
</span></span></code></pre></div><p>And on deployment, specify <code>SERVICE_URL</code> to point at the staging or the production URL of that service.</p>
<h2 id="setting-environment-variables">Setting environment variables</h2>
<p>Now that we’ve seen how to access an environment variable, let’s see how you can set it.</p>
<p>As explained in the documentation about <a href="https://cloud.google.com/workflows/docs/use-environment-variables">custom environment variables</a>, you can use different flags to define, update, delete those variables.</p>
<p>You can specify one or more variables with <code>--set-env-vars</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud workflows deploy WORKFLOW_NAME <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --set-env-vars <span style="color:#bb60d5">KEY1</span><span style="color:#666">=</span>VALUE1,KEY2<span style="color:#666">=</span>VALUE2
</span></span></code></pre></div><p>Use a file that contains all your custom environment variables (one <code>key:value</code> per line):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud workflows deploy WORKFLOW_NAME <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --env-vars-file FILE_PATH
</span></span></code></pre></div><p>You can also update vars with the <code>--update-env-vars</code> flag, remove some with <code>--remove-env-vars</code>, or delete them all with <code>--clear-env-vars</code>.</p>
<h2 id="bonus-tip-use-the-default-built-in-function-in-case-the-environment-variable-isnt-defined">Bonus tip: Use the <code>default()</code> built-in function in case the environment variable isn’t defined</h2>
<p>In case the environment variable wasn’t defined at deployment time, you can use the <code>default()</code> value method to set a default value, like in this example</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">logEnv</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>${default(sys.get_env(&#34;ENVIRONMENT&#34;), &#34;PRODUCTION&#34;)}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, we’re logging the value of the environment, but if the <code>ENVIRONMENT</code> custom environment variable isn’t defined, by default, the value will be <code>PRODUCTION</code>.</p>
<h2 id="summary">Summary</h2>
<p>With custom environment variables, you can parameterize your workflows to tackle different use cases. One of the most frequently used ones is to use those environment variables to distinguish between different environments, like prod or staging. But you can also use them to configure different parameters of your workflow, like defining some configurable limits (number of retries), different endpoints or parameters for the services you call. You define your workflow once, and customize the deployment with different environment variables.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>From Bird to Elephant: Starting a New Journey on Mastodon</title><link>https://glaforge.dev/talks/2023/06/09/from-bird-to-elephant-starting-a-new-journey-on-mastodon/</link><pubDate>Fri, 09 Jun 2023 10:53:29 +0200</pubDate><guid>https://glaforge.dev/talks/2023/06/09/from-bird-to-elephant-starting-a-new-journey-on-mastodon/</guid><description>&lt;script async class="speakerdeck-embed" data-id="365d3694bd674bfa85d812d8c2fd32f9" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>At Devoxx France and Devoxx Greece, I had the pleasure to talk about my new social media journey on &lt;a href="https://joinmastodon.org/">Mastodon&lt;/a>.
After a quick introduction about Mastodon and the &lt;a href="https://fediverse.party/">Fediverse&lt;/a>, I contrasted the key differences between Twitter and Mastodon.
Then I shared some advice on how to get started, how to chose an instance, or clients you can pick from.&lt;/p>
&lt;p>I moved on to important tips to get the best experience on the platform, and ensure to gather a great following:&lt;/p></description><content:encoded>
<![CDATA[<script async class="speakerdeck-embed" data-id="365d3694bd674bfa85d812d8c2fd32f9" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>At Devoxx France and Devoxx Greece, I had the pleasure to talk about my new social media journey on <a href="https://joinmastodon.org/">Mastodon</a>.
After a quick introduction about Mastodon and the <a href="https://fediverse.party/">Fediverse</a>, I contrasted the key differences between Twitter and Mastodon.
Then I shared some advice on how to get started, how to chose an instance, or clients you can pick from.</p>
<p>I moved on to important tips to get the best experience on the platform, and ensure to gather a great following:</p>
<ul>
<li>introduce yourself with a detailed bio and first #introduction post</li>
<li>create your profile before following others</li>
<li>configure your account so it can be discoveredn and recommended to others</li>
<li>contrary to Twitter, you can verify yourself, showing that you own the personal links you share</li>
</ul>
<p>Once your account is ready, if you are migrating from Twitter, you might want to find who among your friends have also migrated, so you can follow them in the fediverse.
I advise people not to delete their Twitter account, to avoid someone to pick up their old handle and to impersonate them.</p>
<p>Another aspect I like about the Mastodon platform is that it seems to care deeply about accessibility, and about people&rsquo;s possible troubles.
Putting <em>alt</em> tags for images is highly encouraged on Mastodon.
Or putting content warnings on text and images also helps prevent unwanted content to jump at your eyse unexpectedly.</p>
<p>In a second part of the presentation, I spoke about the various standards and APIs underlying the Fediverse and Mastodon:</p>
<ul>
<li><a href="https://www.w3.org/TR/activitypub/">ActivityPub</a> and <a href="https://www.w3.org/TR/activitystreams-core/">ActivityStream</a></li>
<li><a href="https://json-ld.org/">JSON-LD</a></li>
<li><a href="https://webfinger.net/">WebFinger</a></li>
<li><a href="http://microformats.org/">MicroFormats</a></li>
<li><a href="https://oauth.net/http-signatures/">HTTP Sigantures</a> and <a href="https://oauth.net/2/">OAuth 2</a>
I went through an exchange between a client and a server, mimicking the actual process when you send a message to another recipient.
This is basically how to implement a basic ActivityPub server, based on Eugen Rochko&rsquo;s great <a href="https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/">post</a> on the topic.</li>
</ul>
<p>The third and final part of the presentation was a comcrete demo on how to implement your own bots on Mastodon.
I showed how to create a service (a <a href="https://micronaut.io/">Micronaut</a> application) that <a href="https://glaforge.dev/posts/2023/01/06/calculating-your-potential-reach-on-mastodon-with-google-cloud-workflows-orchestrating-the-mastodon-apis/">calculates the potential reach</a> of your posts.
You can play with the service <a href="https://stootistics.web.app/">online</a> and give it your account to see how popular your recent posts are.
And there&rsquo;s even an account (<a href="https://tooters.org/@getmyreach">@getmyreach@tooters.org</a>) you can ping on Mastodon to get back the most popular of your posts.
The code is available on <a href="https://github.com/glaforge/stootistics">Github</a> if you want to check it out.</p>
<p>You can check the recording of the talk in English from Devoxx Greece:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/DafHAmlzWUM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And in French at Devoxx France:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/_BaK9BNlUHg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Creating kids stories with Generative AI</title><link>https://glaforge.dev/posts/2023/06/08/creating-kids-stories-with-generative-ai/</link><pubDate>Thu, 08 Jun 2023 12:12:42 +0200</pubDate><guid>https://glaforge.dev/posts/2023/06/08/creating-kids-stories-with-generative-ai/</guid><description>&lt;p>Last week, I wrote about how to &lt;a href="https://glaforge.dev/posts/2023/05/30/getting-started-with-the-palm-api-in-the-java-ecosystem/">get started with the PaLM API in the Java ecosystem&lt;/a>,
and particularly, how to overcome the lack of Java client libraries (at least for now) for the PaLM API, and how to properly authenticate.
However, what I didn&amp;rsquo;t explain was what I was building! Let&amp;rsquo;s fix that today, by telling you a story, a kid story!
Yes, I was using the trendy &lt;strong>Generative AI&lt;/strong> approach to generate bedtime stories for kids.&lt;/p></description><content:encoded>
<![CDATA[<p>Last week, I wrote about how to <a href="https://glaforge.dev/posts/2023/05/30/getting-started-with-the-palm-api-in-the-java-ecosystem/">get started with the PaLM API in the Java ecosystem</a>,
and particularly, how to overcome the lack of Java client libraries (at least for now) for the PaLM API, and how to properly authenticate.
However, what I didn&rsquo;t explain was what I was building! Let&rsquo;s fix that today, by telling you a story, a kid story!
Yes, I was using the trendy <strong>Generative AI</strong> approach to generate bedtime stories for kids.</p>
<p>Without further ado, let me introduce you to my little app: <a href="https://bed-time-stories.web.app/">bedtime stories</a>.</p>
<p><a href="https://bed-time-stories.web.app/"><figure>
  <a href="#img-991ea89b8355fc69be384f27c053673a">
    <img src="/img/bedtime/bedtime-stories-ui.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-991ea89b8355fc69be384f27c053673a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bedtime/bedtime-stories-ui.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Source Code</span>
      </div>
      <div class="admonition-content">
        <p>If you&rsquo;re interested in the source code, head over to the Github <a href="https://github.com/glaforge/bedtimestories">repository</a>:
it&rsquo;s implemented in <a href="https://groovy-lang.org/">Apache Groovy</a>, developed with the <a href="https://micronaut.io/">Micronaut</a> framework,
designed with the <a href="https://shoelace.style/">Shoelace</a> web components, and deployed on Google <a href="https://cloud.run/">Cloud Run</a>, the serverless container runtime.</p>
      </div>
    </div><h2 id="the-concept">The concept</h2>
<p>For a good story, we need 3 key ingredients:</p>
<ul>
<li>a <strong>character</strong> — the main protagonist of the story whose adventures are narrated, like a princess, an astronaut, a firefighter&hellip;</li>
<li>a <strong>setting</strong> — where (and potentially when) the action takes place, like a beautiful kingdom, a faraway planet, a mysterious haunted house&hellip;</li>
<li>a <strong>plot</strong> — a rough idea of what&rsquo;s going to happen in the story, like an evil darkness is menacing the kingdom, huge shooting stars are menacing the planet&hellip;</li>
</ul>
<p>In the UI, there are a few options to pick from, by default, but you can actually customise them at will, or better, come up with your own characters, settings, and plots.
You can play that game with your kids: <em>&ldquo;hey, who should be the hero of our story tonight?&rdquo;</em>. They may have an idea, or even a favorite character!</p>
<p>Then, just click the <code>Generate</code> button, and after 20s or so, you&rsquo;ll have a story ready!</p>
<h2 id="where-generative-ai-comes-in">Where Generative AI comes in</h2>
<p>Of course, the whole story is created thanks to Generative AI.
I used the <a href="https://cloud.google.com/vertex-ai/docs/generative-ai/learn/overview#palm-api">PaLM API</a> for that,
within my Google Cloud project, using the <a href="https://cloud.google.com/vertex-ai/">Vertex AI</a> suite of machine learning services.</p>
<p>For the characters, settings, and plots, I came up with a few ideas on my own.
But I felt like the choice was limited and would warrant some more creativity.
So I asked <a href="http://bard.google.com/">Bard</a> (powered by the PaLM API as well) to help me!
It&rsquo;s all about the <em>art of prompting</em>, of asking the right question to your favorite generative AI:</p>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Prompt</span>
      </div>
      <div class="admonition-content">
        <p>Here are a few characters of bedtime kid stories:</p>
<ul>
<li>a funny little princess with a strong character</li>
<li>a young astronaut exploring space</li>
<li>a fearless firefighter</li>
<li>a cute little cat with a long and silky fur</li>
<li>a gentle dragon with a colorful skin</li>
</ul>
<p>Suggest a list of other possible characters:</p>
      </div>
    </div><p>And lo and behold, I got a dozen suggestions that I then integrated in my character picker. Same thing for the settings and plots.
Interestingly, not only Bard (or PaLM) would give me suggestions, but it also gave me explanations on why those characters, settings and plots made sense.
So Generative AI is also here to help in the process of crafting your application, or your own prompts.</p>
<h2 id="the-story-generator-prompt">The story generator prompt</h2>
<p>The crux of this story is the main prompt that makes the requests to generate the actual story.
I wanted to have a familiar pattern or structure for my generated stories.
You&rsquo;ve probably heard about such common structures for narration, in 3 or 5 acts.
I came across this <a href="https://bubblecow.com/blog/importance-of-structure">website</a> about the 5-act approach,
with an exposition phase, the rising action, the climax, the falling action, and the final denouement.
So when crafting my prompt, first I started by telling PaLM who it was (a story teller), but then I also explained what a 5-act story act looks like,
and finally, I asked it to generate a story for my particular chosen trio of character, setting, and plot.
Here&rsquo;s my final prompt:</p>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Prompt</span>
      </div>
      <div class="admonition-content">
        <p>You are a creative and passionate story teller for kids.
Kids love hearing about the stories you invent.</p>
<p>Your stories are split into 5 acts:</p>
<ul>
<li>Act 1 : Sets up the story providing any contextual background the reader needs, but most importantly it contains the inciting moment. This incident sets the story in motion. An incident forces the protagonist to react. It requires resolution, producing narrative tension.</li>
<li>Act 2 : On a simplistic level this is the obstacles that are placed in the way of the protagonists as they attempt to resolve the inciting incident.</li>
<li>Act 3 : This is the turning point of the story. It is the point of the highest tension. In many modern narratives, this is the big battle or showdown.</li>
<li>Act 4 : The falling action is that part of the story in which the main part (the climax) has finished and you&rsquo;re heading to the conclusion. This is the calm after the tension of the climax.</li>
<li>Act 5 : This is the resolution of the story where conflicts are resolved and loose ends tied up. This is the moment of emotional release for the reader.</li>
</ul>
<p>Generate a kid story in 5 acts, where the protagonist is 
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>c</mi><mi>h</mi><mi>a</mi><mi>r</mi><mi>a</mi><mi>c</mi><mi>t</mi><mi>e</mi><mi>r</mi></mrow><mo separator="true">,</mo><mi>w</mi><mi>h</mi><mi>e</mi><mi>r</mi><mi>e</mi><mi>t</mi><mi>h</mi><mi>e</mi><mi>a</mi><mi>c</mi><mi>t</mi><mi>i</mi><mi>o</mi><mi>n</mi><mi>t</mi><mi>a</mi><mi>k</mi><mi>e</mi><mi>s</mi><mi>p</mi><mi>l</mi><mi>a</mi><mi>c</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">{character}, where the action takes place </annotation></semantics></math></span>

{setting} and ${plot}.</p>
      </div>
    </div><p>The fact of asking PaLM to structure the story that way also influences its textual output.
Not only did it create those 5 key parts, but it also added some bold act labels in its output, which I could filter to then split my string story into 5 smaller chunks.</p>
<h2 id="where-to-go-from-there">Where to go from there?</h2>
<p>I hope you enjoyed the journey so far, and that you got a chance to generate your own story and tell it to a happy kid!</p>
<p>However, for now at least, this is just a concept, so I&rsquo;m not sure whether I&rsquo;ll be developing it much further, but I&rsquo;d like to share possible ways to improve this application.</p>
<ul>
<li>As I explained, it&rsquo;s a story in 5 acts, so we could offer the story over 5 distinct pages that you would have to turn.
Vertex AI also features an image generation service (still in preview for now), so the <strong>stories could also be decorated with AI generated pictures</strong>!
(We can even ask PaLM to generate ideas of prompts for image generation.)</li>
<li>Currently, PaLM can generate up-to 1024 characters of output, but it has 8KB in input.
We can&rsquo;t generate a super long story, but since it&rsquo;s split in 5 acts, that can all fit in the input context window,
we could try to pass PaLM the whole generated story, and ask it 5 times to generate 1KB characters for each section, thus <strong>lengthening the whole story by a factor of 5</strong>.</li>
<li>To go even further, we could use the multilingual capabilities of large language models,
or at least just the Translate API, to offer <strong>translations of the app and the stories</strong> into different languages.</li>
<li>We could also imagine the app being able to narrate the story itself, by taking advantage of <strong>Text-to-Speech voice generation</strong>!
However you might miss on the great bonding opportunity with your kids when you tell them a story,
but on the other hand, kids could entertain themselves when you&rsquo;re busy by generating random stories on their own.</li>
<li>Maybe we could also <strong>save stories</strong> that pleased our kids (and reshare them with others),
as each generation, even with the same trio of character/setting/plot, can yield very diverse outcomes.</li>
</ul>
<p>So there are plenty options possible offered by Generative AI!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Just a handy command-line tool</title><link>https://glaforge.dev/posts/2023/06/07/just-a-handy-command-line-tool/</link><pubDate>Wed, 07 Jun 2023 15:48:02 +0200</pubDate><guid>https://glaforge.dev/posts/2023/06/07/just-a-handy-command-line-tool/</guid><description>&lt;p>When developing new projects on my laptop, I often run some commands over and over again.
Regardless of how far you&amp;rsquo;ve gone with your CI/CD pipelines, running commands locally without resorting to becoming a bash ninja can be pretty easy with&amp;hellip; &lt;code>just&lt;/code>!&lt;/p>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition info">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">&lt;path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>&lt;/svg>
&lt;span>About Just&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>&lt;a href="https://just.systems/">&lt;code>just&lt;/code>&lt;/a> is a handy way to save and run project-specific commands&lt;/p></description><content:encoded>
<![CDATA[<p>When developing new projects on my laptop, I often run some commands over and over again.
Regardless of how far you&rsquo;ve gone with your CI/CD pipelines, running commands locally without resorting to becoming a bash ninja can be pretty easy with&hellip; <code>just</code>!</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>About Just</span>
      </div>
      <div class="admonition-content">
        <p><a href="https://just.systems/"><code>just</code></a> is a handy way to save and run project-specific commands</p>
      </div>
    </div><p>It&rsquo;s a command-line tool that lets you define some commands to run (called recipes), in the form of a Makefile-inspired syntax.
It even allows you to define dependencies between the various tasks of your <code>justfile</code>.
It runs across all environments (Mac, Linux, Windows), and is quick to install.
It loads <code>.env</code> files in which you can define variables specific to your project (other developers can have the same <code>justfile</code> but have variables specific for their projects)</p>
<p>Without further ado, let&rsquo;s see it in action in my current project.</p>
<p>In my project, I have the following <code>justfile</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-make" data-lang="make"><span style="display:flex;"><span><span style="">set</span> <span style="">dotenv-load</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#06287e">alias r </span><span style="color:#666">:</span>= run
</span></span><span style="display:flex;"><span><span style="color:#06287e">alias b </span><span style="color:#666">:</span>= build
</span></span><span style="display:flex;"><span><span style="color:#06287e">alias d </span><span style="color:#666">:</span>= deploy
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#06287e">default</span><span style="color:#666">:</span> run
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#06287e">run</span><span style="color:#666">:</span>
</span></span><span style="display:flex;"><span>    ./gradlew -t run
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#06287e">build</span><span style="color:#666">:</span>
</span></span><span style="display:flex;"><span>    gcloud builds submit -t <span style="color:#bb60d5">$CLOUD_REGION</span>-docker.pkg.dev/<span style="color:#bb60d5">$PROJECT_ID</span>/containers/<span style="color:#bb60d5">$CONTAINER_NAME</span>:v1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#06287e">deploy</span><span style="color:#666">:</span> build
</span></span><span style="display:flex;"><span>     gcloud run deploy bedtimestories --image<span style="color:#666">=</span><span style="color:#bb60d5">$CLOUD_REGION</span>-docker.pkg.dev/<span style="color:#bb60d5">$PROJECT_ID</span>/containers/<span style="color:#bb60d5">$CONTAINER_NAME</span>:v1
</span></span></code></pre></div><ul>
<li>The first instruction tells <code>just</code> to load an <code>.env</code> file.</li>
<li>The (optional) alias lines allow me to define shorcuts for commands that I run very often</li>
<li>There are three commands: <code>run</code>, <code>build</code>, and <code>deploy</code>:
<ul>
<li><code>run</code> will run my application locally with <code>gradle</code></li>
<li><code>build</code> will containerize my app with my <code>Dockerfile</code> on Google Cloud Build</li>
<li><code>deploy</code> depends on <code>build</code> and will deploy my container on Google Cloud Run</li>
</ul>
</li>
</ul>
<p>And now, I <em>just</em> run: <code>just run</code>, <code>just deploy</code>, or their shortcuts: <code>just r</code> or <code>just d</code>.</p>
<p>You also noticed the dollar variables which are interpolated from my <code>.env</code> file which contains the following variables:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#bb60d5">PROJECT_ID</span><span style="color:#666">=</span>some-project-id
</span></span><span style="display:flex;"><span><span style="color:#bb60d5">CLOUD_REGION</span><span style="color:#666">=</span>us-central1
</span></span><span style="display:flex;"><span><span style="color:#bb60d5">CONTAINER_NAME</span><span style="color:#666">=</span>some-container-name
</span></span></code></pre></div><p>It&rsquo;s <code>just</code> a new little handy tool in my toolbox!</p>
<p>Go check it out: <a href="https://just.systems/">just.systems</a>.
And have a look at the <a href="https://cheatography.com/linux-china/cheat-sheets/justfile/">cheat sheet</a> for more examples and syntax.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Getting started with the PaLM API in the Java ecosystem</title><link>https://glaforge.dev/posts/2023/05/30/getting-started-with-the-palm-api-in-the-java-ecosystem/</link><pubDate>Tue, 30 May 2023 15:42:32 +0200</pubDate><guid>https://glaforge.dev/posts/2023/05/30/getting-started-with-the-palm-api-in-the-java-ecosystem/</guid><description>&lt;p>Large Language Models (LLMs for short) are taking the world by storm,
and things like ChatGPT have become very popular and used by millions of users daily.
Google came up with its own chatbot called &lt;a href="https://bard.google.com/">Bard&lt;/a>,
which is powered by its ground-breaking &lt;a href="https://ai.google/discover/palm2/">PaLM 2&lt;/a> model and API.
You can also find and use the PaLM API from withing Google Cloud as well
(as part of &lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/learn/overview">Vertex AI Generative AI&lt;/a> products)
and thus create your own applications based on that API.
However, if you look at the documentation, you&amp;rsquo;ll only find Python tutorials or notebooks,
or also explanations on how to make cURL calls to the API.
But since I&amp;rsquo;m a Java (and Groovy) developer at heart, I was interested in seeing how to do this from the Java world.&lt;/p></description><content:encoded>
<![CDATA[<p>Large Language Models (LLMs for short) are taking the world by storm,
and things like ChatGPT have become very popular and used by millions of users daily.
Google came up with its own chatbot called <a href="https://bard.google.com/">Bard</a>,
which is powered by its ground-breaking <a href="https://ai.google/discover/palm2/">PaLM 2</a> model and API.
You can also find and use the PaLM API from withing Google Cloud as well
(as part of <a href="https://cloud.google.com/vertex-ai/docs/generative-ai/learn/overview">Vertex AI Generative AI</a> products)
and thus create your own applications based on that API.
However, if you look at the documentation, you&rsquo;ll only find Python tutorials or notebooks,
or also explanations on how to make cURL calls to the API.
But since I&rsquo;m a Java (and Groovy) developer at heart, I was interested in seeing how to do this from the Java world.</p>
<h2 id="micronaut--groovy--cloud-run">Micronaut + Groovy + Cloud Run</h2>
<p>My use case was to create a simple application that generates bedtime kid stories, using the PaLM LLM.
I went ahead and decided to use <a href="https://micronaut.io">Micronaut</a> for my framework,
and <a href="https://groovy-lang.org">Apache Groovy</a> for my programming language.
I containerize and deploy my application on <a href="https://cloud.run">Cloud Run</a> on Google cloud.
And I use the <a href="https://cloud.google.com/run/docs/integrate/firebase-hosting">Cloud Run integration for Firebase</a>
to have a nice domain for my app, and to serve my static content from Firebase&rsquo;s CDN.
I won&rsquo;t cover these aspects too much in this article,
but I want to stress the important roadblock you might encounter: authentication.</p>
<h2 id="lets-get-started">Let&rsquo;s get started!</h2>
<p>First, you may not necessarily have access to the Generative AI services in Google Cloud.
For that, you&rsquo;ll need to <a href="https://cloud.google.com/ai/generative-ai">sign up</a> to join the Trusted Tester Program.
But once you have access, you&rsquo;ll be able to use the PaLM API programmatically for your own apps.</p>
<p>When experimenting with prompts to the LLM, you&rsquo;ll notice the handy sliding panel
that shows you how to interact with the API from code. But you only have the choice between Python and cURL.
That said, the cURL command helps you figure out how to call the API via REST:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -X POST <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -H <span style="color:#4070a0">&#34;Authorization: Bearer </span><span style="color:#007020;font-weight:bold">$(</span>gcloud auth print-access-token<span style="color:#007020;font-weight:bold">)</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -H <span style="color:#4070a0">&#34;Content-Type: application/json&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    <span style="color:#4070a0">&#34;https://</span><span style="color:#70a0d0">${</span><span style="color:#bb60d5">API_ENDPOINT</span><span style="color:#70a0d0">}</span><span style="color:#4070a0">/v1/projects/</span><span style="color:#70a0d0">${</span><span style="color:#bb60d5">PROJECT_ID</span><span style="color:#70a0d0">}</span><span style="color:#4070a0">/locations/us-central1/publishers/google/models/</span><span style="color:#70a0d0">${</span><span style="color:#bb60d5">MODEL_ID</span><span style="color:#70a0d0">}</span><span style="color:#4070a0">:predict&#34;</span> -d <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    <span style="color:#4070a0">$&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;instances&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;content&#34;: &#34;Write a kid story about an astronaut visiting another galaxy but facing problems with shooting stars&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    ],
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    &#34;parameters&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;temperature&#34;: 0.5,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;maxOutputTokens&#34;: 1000,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;topP&#34;: 0.8,
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#34;topK&#34;: 40
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    }
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    }&#39;</span>
</span></span></code></pre></div><p>We have the JSON structure in input, and if you call that command,
you&rsquo;ll get an output similar to the following one:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;predictions&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;safetyAttributes&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;scores&#34;</span>: [
</span></span><span style="display:flex;"><span>          <span style="color:#40a070">0.10000000149011612</span>
</span></span><span style="display:flex;"><span>        ],
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;blocked&#34;</span>: <span style="color:#007020;font-weight:bold">false</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;categories&#34;</span>: [
</span></span><span style="display:flex;"><span>          <span style="color:#4070a0">&#34;Violent&#34;</span>
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;content&#34;</span>: <span style="color:#4070a0">&#34;Once upon a time, there was a young astronaut called...&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Our Micronaut will have to marshall/unmarshall those input and output JSON documents.
But the tricky bit for me was authentication.
From the command-line, the embedded <code>gcloud</code> command makes use of an access token,
which grants you access to the PaLM API.
But from my Micronaut/Groovy code, I needed to find a way to authenticate as well.</p>
<h2 id="preparing-a-low-level-http-client-call">Preparing a low-level HTTP client call</h2>
<p>Let&rsquo;s craft the appropriate REST endpoint URI:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> uri <span style="color:#666">=</span> UriBuilder
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">of</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;/v1/projects/${projectId}/locations/us-central1/publishers/google/models/text-bison:predict&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">scheme</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;https&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">host</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;us-central1-aiplatform.googleapis.com&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>Currently, the API is only available in the <code>us-central1</code> region, so it&rsquo;s hard-coded.</p>
<p>Then we need to prepare the request:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> request <span style="color:#666">=</span> HttpRequest
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">POST</span><span style="color:#666">(</span>uri<span style="color:#666">,</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#002070;font-weight:bold">instances:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#666">[</span> <span style="color:#002070;font-weight:bold">content:</span> storyPrompt <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">],</span>
</span></span><span style="display:flex;"><span>                <span style="color:#002070;font-weight:bold">parameters:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#002070;font-weight:bold">temperature:</span> <span style="color:#40a070">0.6</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#002070;font-weight:bold">maxOutputTokens:</span> <span style="color:#40a070">1000</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#002070;font-weight:bold">topP:</span> <span style="color:#40a070">0.8</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#002070;font-weight:bold">topK:</span> <span style="color:#40a070">40</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">bearerAuth</span><span style="color:#666">(</span>token<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">accept</span><span style="color:#666">(</span>MediaType<span style="color:#666">.</span><span style="color:#4070a0">APPLICATION_JSON_TYPE</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">contentType</span><span style="color:#666">(</span>MediaType<span style="color:#666">.</span><span style="color:#4070a0">APPLICATION_JSON_TYPE</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>In a moment, we&rsquo;ll see how we can create the bearer <code>token</code> we use in the <code>bearerAuth()</code> call.
Here, we just send the prompt, with some parameters to say how creative we want the LLM answer to be.</p>
<p>Finally, we make the request:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> predictionResponse <span style="color:#666">=</span> client<span style="color:#666">.</span><span style="color:#4070a0">toBlocking</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">exchange</span><span style="color:#666">(</span>request<span style="color:#666">,</span> PredictionResponse<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">body</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">return</span> predictionResponse<span style="color:#666">.</span><span style="color:#4070a0">predictions</span><span style="color:#666">.</span><span style="color:#4070a0">first</span><span style="color:#666">().</span><span style="color:#4070a0">content</span>
</span></span></code></pre></div><p>I created some classes to unmarshall the resulting JSON:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.fasterxml.jackson.annotation.JsonProperty</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">io.micronaut.serde.annotation.Serdeable</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Serdeable</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">PredictionResponse</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#555;font-weight:bold">@JsonProperty</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;predictions&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    List<span style="color:#666">&lt;</span>Prediction<span style="color:#666">&gt;</span> predictions
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Serdeable</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">Prediction</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#555;font-weight:bold">@JsonProperty</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;safetyAttributes&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    SafetyAttributes safetyAttributes
</span></span><span style="display:flex;"><span>    <span style="color:#555;font-weight:bold">@JsonProperty</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;content&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    String content
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Serdeable</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SafetyAttributes</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><h2 id="authenticating">Authenticating</h2>
<p>When running my application locally, no problem, but once deployed, I needed to have a fresh bearer token.
I created a dedicated service account, with the minimum needed permissions:</p>
<ul>
<li><code>roles/aiplatform.user</code> to have the rights to call the PaLM API</li>
<li><code>roles/logging.logWriter</code> as your Cloud Run app needs to write some logs back to Cloud Logging</li>
</ul>
<p>This <a href="https://medium.com/google-cloud/generative-ai-palm-2-model-deployment-with-cloud-run-54e8a398b24b">article</a>
also nicely explains how to handle deployment to Cloud Run.</p>
<p>My Cloud Run service will be deployed with that service account.</p>
<p>Locally, on my laptop, I used the `GOOGLE_APPLICATION_CREDENTIALS&quot; approach,
by exporting a JSON key, and point at it via an environment variable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#bb60d5">GOOGLE_APPLICATION_CREDENTIALS</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;exported-key.json&#34;</span>
</span></span></code></pre></div><p>You can learn more about local development with
<a href="https://cloud.google.com/docs/authentication/provide-credentials-adc#local-dev">Application Default Credentials</a>.</p>
<p>So locally, we use that exported key, and locally we use a generated token from the restricted service account.
And to generate that token, I had to use the
<a href="https://github.com/googleapis/google-auth-library-java#google-auth-library-oauth2-http">google-auth-library-oauth2-http</a></p>
<p>Here&rsquo;s the missing snippet to do so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> credentials <span style="color:#666">=</span> GoogleCredentials<span style="color:#666">.</span><span style="color:#4070a0">applicationDefault</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">createScoped</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;https://www.googleapis.com/auth/cloud-platform&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>credentials<span style="color:#666">.</span><span style="color:#4070a0">refreshIfExpired</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> token <span style="color:#666">=</span> credentials<span style="color:#666">.</span><span style="color:#4070a0">accessToken</span><span style="color:#666">.</span><span style="color:#4070a0">tokenValue</span>
</span></span></code></pre></div><p>To import that authentication library in my project, I defined its requirement in my <code>build.gradle</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>implementation<span style="color:#666">(</span><span style="color:#4070a0">&#39;com.google.auth:google-auth-library-credentials:1.17.0&#39;</span><span style="color:#666">)</span>
</span></span></code></pre></div><h2 id="voilà">Voilà!</h2>
<p>With the right authentication client library, I was able to create the beared token needed to authenticate
to the Vertex AI PaLM API, both locally on my laptop, and once deployed on Cloud Run as well.</p>
<p>Hopefully, when Google releases official Java client libraries,
it&rsquo;ll certainly be easier to interact with the PaLM API,
without having to create marshalling/unmarshalling code,
and will likely make it smoother to authenticate transparently.
So stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Exploring Open Location Code</title><link>https://glaforge.dev/posts/2023/03/28/exploring-open-location-code/</link><pubDate>Tue, 28 Mar 2023 09:34:17 +0200</pubDate><guid>https://glaforge.dev/posts/2023/03/28/exploring-open-location-code/</guid><description>&lt;p>&lt;figure>
&lt;a href="#img-a3b04335fbe30698bff005c417f53821">
&lt;img src="https://glaforge.dev/img/misc/eiffel-tower-plus-code.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-a3b04335fbe30698bff005c417f53821">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/eiffel-tower-plus-code.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>When using Google Maps, you might have seen those strange little codes, as in the screenshot above.
This is a &lt;em>plus code&lt;/em>, or to use the more official name, an &lt;strong>Open Location Code&lt;/strong>.
It&amp;rsquo;s a way to encode a location in a short and (somewhat) memorable form.&lt;/p>
&lt;p>In countries like France, every house has an official address, so you can easily receive letters or get some parcel delivered. But there are countries where no such location system exists, so you have to resort to describing where you live (take this road, turn right after the red house, etc.)&lt;/p></description><content:encoded>
<![CDATA[<p><figure>
  <a href="#img-a3b04335fbe30698bff005c417f53821">
    <img src="/img/misc/eiffel-tower-plus-code.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a3b04335fbe30698bff005c417f53821">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/eiffel-tower-plus-code.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>When using Google Maps, you might have seen those strange little codes, as in the screenshot above.
This is a <em>plus code</em>, or to use the more official name, an <strong>Open Location Code</strong>.
It&rsquo;s a way to encode a location in a short and (somewhat) memorable form.</p>
<p>In countries like France, every house has an official address, so you can easily receive letters or get some parcel delivered. But there are countries where no such location system exists, so you have to resort to describing where you live (take this road, turn right after the red house, etc.)</p>
<p>Of coursse, you could use GPS coordinates, but that&rsquo;s not very convenient to share, and nobody could remember a precise address. So there have been several attemps at creating systems that represent any location in the world,
like <a href="http://geohash.org/">GeoHash</a>, <a href="https://www.mapcode.com/">MapCode</a>, and other proprietary systems like 3-words.</p>
<p>Out of curiosity, I wanted to play with this geo-encoding approach, and decided to spend a few minutes playing with the available Java library (available on <a href="https://central.sonatype.com/artifact/com.google.openlocationcode/openlocationcode/1.0.4">Maven Central</a>), but using <a href="https://groovy-lang.org/">Apache Groovy</a>.</p>
<p>You&rsquo;ll find more links on the topic at the end of this article.</p>
<h2 id="playing-with-plus-codes-in-groovy">Playing with plus codes in Groovy</h2>
<p>Here&rsquo;s a little script that shows the library in action:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;com.google.openlocationcode:openlocationcode:1.0.4&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.openlocationcode.OpenLocationCode</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Eiffel Tower
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#902000">def</span> <span style="color:#666">(</span>lat<span style="color:#666">,</span> lon<span style="color:#666">)</span> <span style="color:#666">=</span> <span style="color:#666">[</span><span style="color:#40a070">48.8584</span><span style="color:#666">,</span> <span style="color:#40a070">2.29447</span><span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> eiffelTowerPlusCode <span style="color:#666">=</span> OpenLocationCode<span style="color:#666">.</span><span style="color:#4070a0">encode</span><span style="color:#666">(</span>lat<span style="color:#666">,</span> lon<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>println <span style="color:#4070a0">&#34;Eiffel Tower +code: ${eiffelTowerPlusCode}&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> decoded <span style="color:#666">=</span> OpenLocationCode<span style="color:#666">.</span><span style="color:#4070a0">decode</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;8FW4V75V+9Q&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>println <span style="color:#4070a0">&#34;Original coord: ${decoded.centerLatitude}, ${decoded.centerLongitude}&#34;</span>
</span></span></code></pre></div><p>(You can play and run the above script in the <a href="https://gwc-experiment.appspot.com/?g=groovy_4_0&amp;gist=4176e0ad13b396001c92ab5cd584b3d8">Groovy Web Console</a>)</p>
<h2 id="more-information">More information</h2>
<ul>
<li>The official <a href="https://maps.google.com/pluscodes/">Open Location Code website</a></li>
<li>The project <a href="https://github.com/google/open-location-code">open-location-code project on Github</a> that provides implementations of the algorithm in various programming languages</li>
<li>A <a href="https://github.com/google/open-location-code/wiki/Evaluation-of-Location-Encoding-Systems">comparison</a> of different geocoding / geohashing systems</li>
<li>The French wikipedia <a href="https://fr.wikipedia.org/wiki/Open_Location_Code">page on Open Location Code</a> shows visually how the world map is cut in smaller boxes, as you zoom in, and takes the example of the Eiffel Tower like in my script above</li>
<li>For reference, the English wikipedia <a href="https://en.wikipedia.org/wiki/Open_Location_Code">page</a>, but it&rsquo;s a little less detailed and visual</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>cURL's --json flag</title><link>https://glaforge.dev/posts/2023/03/22/curl-s-json-flag/</link><pubDate>Wed, 22 Mar 2023 07:14:06 +0100</pubDate><guid>https://glaforge.dev/posts/2023/03/22/curl-s-json-flag/</guid><description>&lt;p>As cURL was celebrating its &lt;a href="https://daniel.haxx.se/blog/2023/03/20/twenty-five-years-of-curl/">25th birthday&lt;/a>, I was reading Daniel Stenberg&amp;rsquo;s story behind the project, and discovered a neat little feature I hadn&amp;rsquo;t heard of before: the &lt;code>--json&lt;/code> flag! Daniel even &lt;a href="https://daniel.haxx.se/blog/2022/02/02/curl-dash-dash-json/">blogged&lt;/a> about it when it landed in cURL 7.82.0 last year.&lt;/p>
&lt;p>So what&amp;rsquo;s so cool about it? If you&amp;rsquo;re like me, you&amp;rsquo;re used to post some JSON data with the following verbose approach:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>curl --data &lt;span style="color:#4070a0">&amp;#39;{&amp;#34;msg&amp;#34;: &amp;#34;hello&amp;#34;}&amp;#39;&lt;/span> &lt;span style="color:#4070a0;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0;font-weight:bold">&lt;/span> --header &lt;span style="color:#4070a0">&amp;#34;Content-Type: application/json&amp;#34;&lt;/span> &lt;span style="color:#4070a0;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0;font-weight:bold">&lt;/span> --header &lt;span style="color:#4070a0">&amp;#34;Accept: application/json&amp;#34;&lt;/span> &lt;span style="color:#4070a0;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4070a0;font-weight:bold">&lt;/span> https://example.com
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You have to pass the data, and also pass headers to specify the content-type.
You can make it slightly shorter with the one-letter flags:&lt;/p></description><content:encoded>
<![CDATA[<p>As cURL was celebrating its <a href="https://daniel.haxx.se/blog/2023/03/20/twenty-five-years-of-curl/">25th birthday</a>, I was reading Daniel Stenberg&rsquo;s story behind the project, and discovered a neat little feature I hadn&rsquo;t heard of before: the <code>--json</code> flag! Daniel even <a href="https://daniel.haxx.se/blog/2022/02/02/curl-dash-dash-json/">blogged</a> about it when it landed in cURL 7.82.0 last year.</p>
<p>So what&rsquo;s so cool about it? If you&rsquo;re like me, you&rsquo;re used to post some JSON data with the following verbose approach:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl --data <span style="color:#4070a0">&#39;{&#34;msg&#34;: &#34;hello&#34;}&#39;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --header <span style="color:#4070a0">&#34;Content-Type: application/json&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --header <span style="color:#4070a0">&#34;Accept: application/json&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    https://example.com
</span></span></code></pre></div><p>You have to pass the data, and also pass headers to specify the content-type.
You can make it slightly shorter with the one-letter flags:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -d <span style="color:#4070a0">&#39;{&#34;msg&#34;: &#34;hello&#34;}&#39;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -H <span style="color:#4070a0">&#34;Content-Type: application/json&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    -H <span style="color:#4070a0">&#34;Accept: application/json&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    https://example.com
</span></span></code></pre></div><p>But with the recent addition of this flag, it&rsquo;s much shorter, as you don&rsquo;t have to specify the mime types:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl --json <span style="color:#4070a0">&#39;{&#34;msg&#34;: &#34;hello&#34;}&#39;</span> https://example.com
</span></span></code></pre></div><p>It&rsquo;s available since version 7.82.0, and on my Mac laptop, I have version 7.86.0.</p>
<p>For reference, here&rsquo;s the excerpt of the manual that gives the details about the <code>--json</code> flag:</p>
<pre tabindex="0"><code>--json &lt;data&gt;
    (HTTP) Sends the specified JSON data in a POST request to the
    HTTP server. --json works as a shortcut for passing on these
    three options:

    --data [arg]
    --header &#34;Content-Type: application/json&#34;
    --header &#34;Accept: application/json&#34;

    There is no verification that the passed in data is actual
    JSON or that the syntax is correct.

    If you start the data with the letter @, the rest should be a
    file name to read the data from, or a single dash (-) if you
    want curl to read the data from stdin. Posting data from a file
    named &#39;foobar&#39; would thus be done with --json @foobar and to
    instead read the data from stdin, use --json @-.

    If this option is used more than once on the same command line,
    the additional data pieces will be concatenated to the previous
    before sending.

    The headers this option sets can be overridden with --header
    as usual.

    --json can be used several times in a command line

    Examples:

    curl --json &#39;{ &#34;drink&#34;: &#34;coffe&#34; }&#39; https://example.com
    curl --json &#39;{ &#34;drink&#34;:&#39; --json &#39; &#34;coffe&#34; }&#39; https://example.com
    curl --json @prepared https://example.com
    curl --json @- https://example.com &lt; json.txt
</code></pre><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tip: Visualize output in the Groovy Console</title><link>https://glaforge.dev/posts/2023/03/21/tip-visualize-output-in-groovy-console/</link><pubDate>Tue, 21 Mar 2023 15:00:47 +0100</pubDate><guid>https://glaforge.dev/posts/2023/03/21/tip-visualize-output-in-groovy-console/</guid><description>&lt;p>For some scripting tasks, my favorite go-to tool is the &lt;strong>Groovy Console&lt;/strong>,
and writing code with &lt;a href="https://groovy-lang.org/">Apache Groovy&lt;/a>.
Usually, you just spill some &lt;code>println&lt;/code> calls all over the place to display some textual information.
But there&amp;rsquo;s a little known secret. Not really secret though,
as it&amp;rsquo;s properly &lt;a href="http://docs.groovy-lang.org/2.4.1/html/documentation/tools-groovyconsole.html#GroovyConsole-Visualizingscriptoutputresults">documented&lt;/a>.
It&amp;rsquo;s possible to display images (like &lt;code>BufferedImage&lt;/code> or its parent &lt;code>java.awt.Image&lt;/code>)
or all sorts of rich components (from the &lt;code>Swing&lt;/code> UI toolkit, like &lt;code>JPanel&lt;/code>, &lt;code>JLabel&lt;/code>, etc.)&lt;/p></description><content:encoded>
<![CDATA[<p>For some scripting tasks, my favorite go-to tool is the <strong>Groovy Console</strong>,
and writing code with <a href="https://groovy-lang.org/">Apache Groovy</a>.
Usually, you just spill some <code>println</code> calls all over the place to display some textual information.
But there&rsquo;s a little known secret. Not really secret though,
as it&rsquo;s properly <a href="http://docs.groovy-lang.org/2.4.1/html/documentation/tools-groovyconsole.html#GroovyConsole-Visualizingscriptoutputresults">documented</a>.
It&rsquo;s possible to display images (like <code>BufferedImage</code> or its parent <code>java.awt.Image</code>)
or all sorts of rich components (from the <code>Swing</code> UI toolkit, like <code>JPanel</code>, <code>JLabel</code>, etc.)</p>
<p>For example, to display an image in the output pane of my Groovy Console, I can load it up via an <code>ImageIcon</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">javax.swing.*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> url <span style="color:#666">=</span> <span style="color:#4070a0">&#34;https://pbs.twimg.com/profile_images/1590794600867893271/ttqX3njd_400x400.jpg&#34;</span><span style="color:#666">.</span><span style="color:#4070a0">toURL</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">new</span> <span style="color:#06287e">ImageIcon</span><span style="color:#666">(</span>url<span style="color:#666">)</span>
</span></span></code></pre></div><p>For that purpose, you&rsquo;ll have to ensure that the <code>View &gt; Visualize Script Results</code> is enabled, as shown in the picture below:</p>
<p><figure>
  <a href="#img-99e005b6247319dd1a6ce48a06daa76b">
    <img src="/img/misc/groovy-console-visualize-output.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-99e005b6247319dd1a6ce48a06daa76b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/groovy-console-visualize-output.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>As mentioned in the <a href="http://docs.groovy-lang.org/2.4.1/html/documentation/tools-groovyconsole.html#GroovyConsole-Visualizingscriptoutputresults">documentation</a>, you could for example display maps or lists as nice Swing <code>JTable</code>.
Or for some data visualisation, you could also used any Java libraries that output images or that can be embeded in Swing components,
like the venerable JFreeChart library (ie. here&rsquo;s a StackOverflow question that shows that JFreeChart charts can be embedded in Swing components like <code>JPanel</code>).</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Some custom VS Code settings</title><link>https://glaforge.dev/posts/2023/03/08/some-custom-vscode-settings/</link><pubDate>Wed, 08 Mar 2023 22:34:43 +0100</pubDate><guid>https://glaforge.dev/posts/2023/03/08/some-custom-vscode-settings/</guid><description>&lt;p>I regularly use both &lt;a href="https://www.jetbrains.com/idea/">IntelliJ IDEA&lt;/a> and &lt;a href="https://code.visualstudio.com/">Visual Studio Code&lt;/a> as my environments for developing.
But like all tools, we often need to personalise them to our liking, to feel at ease, or to be more productive.
As we read code more than we write, there are certain settings in your favorite editor to improve your reading experience. Today, I&amp;rsquo;ll share of the tweaks I&amp;rsquo;ve made to my VS Code settings.&lt;/p>
&lt;p>You can edit some of the settings by opening the UI of the settings dialog box, but you can also edit the &lt;code>JSON&lt;/code> file in which those settings are saved. On a Mac, for example, the &lt;code>settings.json&lt;/code> file is stored in &lt;code>~/Library/Application Support/Code/User/&lt;/code>.&lt;/p></description><content:encoded>
<![CDATA[<p>I regularly use both <a href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a> and <a href="https://code.visualstudio.com/">Visual Studio Code</a> as my environments for developing.
But like all tools, we often need to personalise them to our liking, to feel at ease, or to be more productive.
As we read code more than we write, there are certain settings in your favorite editor to improve your reading experience. Today, I&rsquo;ll share of the tweaks I&rsquo;ve made to my VS Code settings.</p>
<p>You can edit some of the settings by opening the UI of the settings dialog box, but you can also edit the <code>JSON</code> file in which those settings are saved. On a Mac, for example, the <code>settings.json</code> file is stored in <code>~/Library/Application Support/Code/User/</code>.</p>
<p>Here are my current custom settings, compared to the default configuration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;security.workspace.trust.untrustedFiles&#34;</span>: <span style="color:#4070a0">&#34;open&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;window.title&#34;</span>: <span style="color:#4070a0">&#34;${folderPath} ${separator} ${activeEditorShort}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;breadcrumbs.enabled&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;workbench.colorCustomizations&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;[Default Dark+]&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;editor.lineHighlightBackground&#34;</span>: <span style="color:#4070a0">&#34;#404020FF&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;editorLineNumber.activeForeground&#34;</span>: <span style="color:#4070a0">&#34;#ffff00&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;[Default Light+]&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;editor.lineHighlightBackground&#34;</span>: <span style="color:#4070a0">&#34;#FFEEEE&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;editorLineNumber.activeForeground&#34;</span>: <span style="color:#4070a0">&#34;#ff0000&#34;</span>,
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;workbench.tree.renderIndentGuides&#34;</span>: <span style="color:#4070a0">&#34;always&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;workbench.tree.indent&#34;</span>: <span style="color:#40a070">10</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;editor.fontSize&#34;</span>: <span style="color:#40a070">12</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;editor.fontFamily&#34;</span>: <span style="color:#4070a0">&#34;Fira Code&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;editor.fontLigatures&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;editor.formatOnPaste&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;editor.guides.bracketPairs&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;files.trimTrailingWhitespace&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;editor.mouseWheelZoom&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s go through them:</p>
<ul>
<li><code>security.workspace.trust.untrustedFiles</code> — allows to open a file not part of the project without warning</li>
<li><code>window.title</code> — for a custom window title, with the root of the project, and the name of the current opened file</li>
<li><code>breadcrumbs.enabled</code> — to display a breadcrumbe to see where I&rsquo;m located in the file tree, and inside the file&rsquo;s structure itself</li>
<li><code>editor.lineHighlightBackground</code> and <code>editorLineNumber.activeForeground</code> — I&rsquo;ve customised the colors of the current line the cursor is on, as well as the line number in the gutter, to make it stand out more on my dark background</li>
<li><code>workbench.tree.renderIndentGuides</code> and <code>workbench.tree.indent</code> — adds the little vertical bars in the file explorer, to visually see the current level or depth in the directory structure, and specifies how far apart they should be</li>
<li><code>editor.fontSize</code> — customize the default font size (but it may already be 12 by default)</li>
<li><code>editor.fontFamily</code> and <code>editor.fontLigatures</code> — I&rsquo;m using Fire Code for my coding font, and I enable the ligatures to have nice looking operators</li>
<li><code>editor.formatOnPaste</code> — to automatically format the code that you pase, without having to manually reident it</li>
<li><code>editor.guides.bracketPairs</code> — for drawing a little vertical line that highlights the current scope of the block my cursor is in</li>
<li><code>files.trimTrailingWhitespace</code> — I like to trim the trailing whitespace at the end of a line automatically (to avoid some dummy commit because of a remaining space, for instance)</li>
<li><code>editor.mouseWheelZoom</code> — to allow mousewheel zoom in and out to increase / decrease font size</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>New blog location</title><link>https://glaforge.dev/posts/2023/03/06/new-blog-location/</link><pubDate>Mon, 06 Mar 2023 13:30:49 +0100</pubDate><guid>https://glaforge.dev/posts/2023/03/06/new-blog-location/</guid><description>&lt;p>I started blogging 20 years ago, in April 2003. My first blog engine was a PHP CMS, called &lt;a href="http://nucleuscms.org/">Nucleus&lt;/a>. I was hosting it on my ISP, at free.fr.&lt;/p>
&lt;p>Then in 2011, I wrote my own blog engine, called &lt;a href="https://github.com/glaforge/bloogaey">Bloogaey&lt;/a>, which was written in &lt;a href="https://groovy-lang.org/">Groovy&lt;/a>, using my little &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a> web framework, and running on &lt;a href="https://cloud.google.com/appengine">App Engine&lt;/a>.&lt;/p>
&lt;p>As it became a bit painful to properly format my blog posts, and evolve my blog engine, I decided I should move to something that is more static, with a static site generator that eats Markdown files: I chose the &lt;a href="https://gohugo.io/">Hugo&lt;/a> static site generator that I used in some previous projects. And I&amp;rsquo;m now hosting the content of my blog in &lt;a href="https://github.com/glaforge/glaforge.github.io">Github Pages&lt;/a>, under the &lt;a href="https://glaforge.dev/">glaforge.dev&lt;/a> custom domain name.&lt;/p></description><content:encoded>
<![CDATA[<p>I started blogging 20 years ago, in April 2003. My first blog engine was a PHP CMS, called <a href="http://nucleuscms.org/">Nucleus</a>. I was hosting it on my ISP, at free.fr.</p>
<p>Then in 2011, I wrote my own blog engine, called <a href="https://github.com/glaforge/bloogaey">Bloogaey</a>, which was written in <a href="https://groovy-lang.org/">Groovy</a>, using my little <a href="http://gaelyk.appspot.com/">Gaelyk</a> web framework, and running on <a href="https://cloud.google.com/appengine">App Engine</a>.</p>
<p>As it became a bit painful to properly format my blog posts, and evolve my blog engine, I decided I should move to something that is more static, with a static site generator that eats Markdown files: I chose the <a href="https://gohugo.io/">Hugo</a> static site generator that I used in some previous projects. And I&rsquo;m now hosting the content of my blog in <a href="https://github.com/glaforge/glaforge.github.io">Github Pages</a>, under the <a href="https://glaforge.dev/">glaforge.dev</a> custom domain name.</p>
<h2 id="httpsglaforgedev"><a href="https://glaforge.dev/">https://glaforge.dev/</a></h2>
<p>I&rsquo;ll stop blogging on the old website, and will write my new articles on that new URL.</p>
<p>I&rsquo;ll try to redirect all the links back to this new location, and also update the RSS feed for those who are subscribed via Feedburner. But if you want to update your feed reader manually, here&rsquo;s the new URL for the feed: <a href="https://glaforge.dev/index.xml">https://glaforge.dev/index.xml</a></p>
<p><a href="https://glaforge.dev/"><figure>
  <a href="#img-6f45cb2e0ed5043791a5fdcebf468780">
    <img src="/img/misc/glaforge-dev-screenshot.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6f45cb2e0ed5043791a5fdcebf468780">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/glaforge-dev-screenshot.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Google Cloud Workflows API automation, patterns, and best practices</title><link>https://glaforge.dev/talks/2023/02/01/google-cloud-workflows-api-automation-patterns-best-practices/</link><pubDate>Wed, 01 Feb 2023 23:31:37 +0100</pubDate><guid>https://glaforge.dev/talks/2023/02/01/google-cloud-workflows-api-automation-patterns-best-practices/</guid><description>&lt;ul>
&lt;li>Workflows at a glance, benefits, key features, use cases&lt;/li>
&lt;li>UI interface in Google Cloud console&lt;/li>
&lt;li>Deep dive into the Workflows syntax&lt;/li>
&lt;li>Workflows connectors&lt;/li>
&lt;li>Demos&lt;/li>
&lt;li>Patterns and best practices&lt;/li>
&lt;/ul>
&lt;script async class="speakerdeck-embed" data-id="9957977f37554ccdacf91f0859592bae" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script></description><content:encoded>
<![CDATA[<ul>
<li>Workflows at a glance, benefits, key features, use cases</li>
<li>UI interface in Google Cloud console</li>
<li>Deep dive into the Workflows syntax</li>
<li>Workflows connectors</li>
<li>Demos</li>
<li>Patterns and best practices</li>
</ul>
<script async class="speakerdeck-embed" data-id="9957977f37554ccdacf91f0859592bae" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Calculating your potential reach on Mastodon with Google Cloud Workflows orchestrating the Mastodon APIs</title><link>https://glaforge.dev/posts/2023/01/06/calculating-your-potential-reach-on-mastodon-with-google-cloud-workflows-orchestrating-the-mastodon-apis/</link><pubDate>Fri, 06 Jan 2023 18:10:20 +0100</pubDate><guid>https://glaforge.dev/posts/2023/01/06/calculating-your-potential-reach-on-mastodon-with-google-cloud-workflows-orchestrating-the-mastodon-apis/</guid><description>&lt;p>With the turmoil around Twitter, like many, I’ve decided to look into &lt;a href="https://joinmastodon.org/">Mastodon&lt;/a>.
My friend &lt;a href="https://uwyn.net/@gbevin">Geert&lt;/a> is running his own Mastodon server,
and welcomed me on his instance at: &lt;a href="https://uwyn.net/@glaforge">uwyn.net/@glaforge&lt;/a>.&lt;/p>
&lt;p>With Twitter, you can access your &lt;a href="https://analytics.twitter.com/about">analytics&lt;/a> to know how your tweets are doing, how many views you’re getting.
Working in developer relations, it’s always interesting to get some insights into those numbers to figure out if what you’re sharing is interesting for your community.
But for various (actually good) reasons, Mastodon doesn’t offer such detailed analytics.
However, I wanted to see what the &lt;a href="https://docs.joinmastodon.org/api/">Mastodon APIs&lt;/a> offered.&lt;/p></description><content:encoded>
<![CDATA[<p>With the turmoil around Twitter, like many, I’ve decided to look into <a href="https://joinmastodon.org/">Mastodon</a>.
My friend <a href="https://uwyn.net/@gbevin">Geert</a> is running his own Mastodon server,
and welcomed me on his instance at: <a href="https://uwyn.net/@glaforge">uwyn.net/@glaforge</a>.</p>
<p>With Twitter, you can access your <a href="https://analytics.twitter.com/about">analytics</a> to know how your tweets are doing, how many views you’re getting.
Working in developer relations, it’s always interesting to get some insights into those numbers to figure out if what you’re sharing is interesting for your community.
But for various (actually good) reasons, Mastodon doesn’t offer such detailed analytics.
However, I wanted to see what the <a href="https://docs.joinmastodon.org/api/">Mastodon APIs</a> offered.</p>
<h2 id="how-to-calculate-your-potential-reach">How to calculate your potential reach</h2>
<p>Your <em>“toots”</em> (ie. your posts on Mastodon) can be “boosted” (equivalent of a retweet on Twitter).
Also, each actor on Mastodon has a certain <strong>number of followers</strong>.
So potentially, one of your toots can reach all your followers, as well as all the followers of the actors who reshare your toot.</p>
<p>So the maximum potential reach of one of your posts would correspond to the following equation:</p>
<pre tabindex="0"><code>potential_reach = 
    me.followers_count + 
    ∑ ( boosters[i].followers_count )
</code></pre><p>Let’s play with the Mastodon APIs to compute your reach</p>
<p>Fortunately, the Mastodon APIs allow you to get those numbers, albeit not with a single API call.
Let’s have a look at the interesting endpoints to get the potential reach of my most recent posts.</p>
<p>First of all, I’ll look up my account on the Mastodon instance that hosts me:</p>
<pre tabindex="0"><code>GET https://uwyn.net/api/v1/accounts/lookup?acct=glaforge
</code></pre><p>I pass my account name as a query parameter to the /accounts/lookup endpoint.</p>
<p>In return, I get a JSON document that contains various details about my account and me
(I’ll just show some of the interesting fields, not the whole payload):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="">id:</span> <span style="color:#062873;font-weight:bold">&#34;109314675907601286&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">username:</span> <span style="color:#062873;font-weight:bold">&#34;glaforge&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">acct:</span> <span style="color:#062873;font-weight:bold">&#34;glaforge&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">display_name:</span> <span style="color:#062873;font-weight:bold">&#34;Guillaume Laforge&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">...</span>
</span></span><span style="display:flex;"><span>    <span style="">note:</span> <span style="color:#062873;font-weight:bold">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">url:</span> <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">...</span>
</span></span><span style="display:flex;"><span>    <span style="">followers_count:</span> <span style="">878,</span>
</span></span><span style="display:flex;"><span>    <span style="">fields:</span> <span style="">[...]</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I get two important pieces of information here: the followers_count gives me, you guessed it,
the number of followers my account has, thus the potential number of persons that can see my toots.
Also the id of my account, which I’ll need for some further API calls further down.</p>
<p>To get the most recent statuses I’ve posted, I’ll indeed need that account id for crafting the new URL I’ll call:</p>
<pre tabindex="0"><code>GET https://uwyn.net/api/v1/accounts/109314675907601286/statuses
</code></pre><p>This call will return a list of statuses (again, snipped less interesting part of the payload):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>    <span style="">…</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="">id:</span> <span style="color:#062873;font-weight:bold">&#34;109620174916140649&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">created_at:</span> <span style="color:#062873;font-weight:bold">&#34;2023-01-02T14:52:06.044Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">…</span>
</span></span><span style="display:flex;"><span>        <span style="">replies_count:</span> <span style="">2,</span>
</span></span><span style="display:flex;"><span>        <span style="">reblogs_count:</span> <span style="">6,</span>
</span></span><span style="display:flex;"><span>        <span style="">favourites_count:</span> <span style="">6,</span>
</span></span><span style="display:flex;"><span>        <span style="">…</span>
</span></span><span style="display:flex;"><span>        <span style="">edited_at:</span> <span style="">null,</span>
</span></span><span style="display:flex;"><span>        <span style="">content:</span> <span style="color:#062873;font-weight:bold">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">reblog:</span> <span style="">null,</span>
</span></span><span style="display:flex;"><span>        <span style="">…</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="">…</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>In each status object, you can see the number of replies, the number of times the post was reshared or favorited,
or whether it’s a reshared toot itself. So what’s interesting here is the reblogs_count number.</p>
<p>However, you don’t get more details about who reshared your toot.
So we’ll need some extra calls to figure that out!</p>
<p>So for each of your posts, you’ll have to call the following endpoint to know more about those “reblogs”:</p>
<pre tabindex="0"><code>GET https://uwyn.net/api/v1/statuses/109620174916140649/reblogged_by
</code></pre><p>This time, you’ll get a list of all the persons who reshared your post:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="">id:</span> <span style="color:#062873;font-weight:bold">&#34;123456789&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">username:</span> <span style="color:#062873;font-weight:bold">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">acct:</span> <span style="color:#062873;font-weight:bold">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">display_name:</span> <span style="color:#062873;font-weight:bold">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="">...</span>
</span></span><span style="display:flex;"><span>        <span style="">followers_count:</span> <span style="">7,</span>
</span></span><span style="display:flex;"><span>        <span style="">...</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="">...</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>And as you can see the details of those persons also have the followers_count field,
that tells the number of people that follow them.</p>
<p>So now, we have all the numbers we need to calculate the potential reach of our toots:
your own number of followers, and the number of followers of all those who reshared!
It doesn’t mean that your toots will actually be viewed that many times,
as one doesn’t necessarily read each and every toots on their timelines,
but at least, that’s an approximation of the maximum reach you can get.</p>
<h2 id="automating-the-potential-reach-calculation-with-web-api-orchestration">Automating the potential reach calculation with Web API orchestration</h2>
<p>Initially I played with both cURL and a little Apache Groovy <a href="https://gwc-experiment.appspot.com/?g=groovy_4_0&amp;codez=eJx1kl9PwjAUxd_3KRqeNjUtoAlsCTHGhAfjg0F8MoaUcQczY3dp78DF-N0tLRv_xp66c-85t_216bpARWypEDcV_9aY8xvPW0DCdms2Yjls2YtZvmelKkD5gcfMZzsIkT5UZpo6K6JCR0KU2yrnOZCQRSo2PaFJUqlBi143vB_2hmE4fOiH_cEg7HZsRiGVhsXUJJkYO94qU_ghf5_PCT8mr37AyYiB14x-Q02gxphluAWljf8QxmUcY5kTT-ryzP6zx4h1m4Sx3KBKCc68iZFLq7eZJjDPcHlmUU487rcGp08bUDWyW9YRrraExWxeHcNwA56UktUlk5PANjJNagPm2e5pdJnOY9MCMbFflraTYn9cl2s_2J9IAZUqZ5_2CSQ1vOiU5Z2r1mlR2125nj206BirqxCSzJwyPgRdOZqzFEiQU7qzyXjVOtVQvxLhfXneP1NZD_M">script</a>
to better understand the Mastodon APIs to figure out how to chain them to get to the expected result.
Then I decided to automate that series of Web API calls using an API orchestrator: <a href="https://cloud.google.com/workflows">Google Cloud Workflows</a>.</p>
<p>To recap, we need to:</p>
<ul>
<li>Get the details of your account</li>
<li>Get the recent posts for that account</li>
<li>Get all the followers count for each person who reshared each post</li>
</ul>
<p>Let’s have a look at this piece by piece:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">account_server_vars</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">account</span>:<span style="color:#bbb"> </span>${input.account}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">server</span>:<span style="color:#bbb"> </span>${input.server}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">prefix</span>:<span style="color:#bbb"> </span>${&#34;https://&#34; + server + &#34;/api/v1&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">impact_map</span>:<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span></code></pre></div><p>First, the workflow takes an account and server arguments, in my case that is glaforge and uwyn.net.
And I’m defining a variable with the base path of the Mastodon API, and a dictionary to hold the data for each toot.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">find_account_id</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${prefix + &#34;/accounts/lookup&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">acct</span>:<span style="color:#bbb"> </span>${account}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>account_id_lookup<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">account_id_var</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">account_id</span>:<span style="color:#bbb"> </span>${account_id_lookup.body.id}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">followers_count</span>:<span style="color:#bbb"> </span>${account_id_lookup.body.followers_count}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Above, I’m doing an account lookup, to get the id of the account, but also the followers count.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">get_statuses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${prefix + &#34;/accounts/&#34; + account_id + &#34;/statuses&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">limit</span>:<span style="color:#bbb"> </span><span style="color:#40a070">100</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">exclude_reblogs</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>statuses<span style="color:#bbb">
</span></span></span></code></pre></div><p>We get the list of most recent toots.</p>
<p>Now things get more interesting, as we need to iterate over all the statuses. We’ll do so in parallel, to save some time:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">iterate_statuses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">parallel</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">shared</span>:<span style="color:#bbb"> </span>[impact_map]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">for</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">value</span>:<span style="color:#bbb"> </span>status<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span>${statuses.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>To parallelize the per-status calls, we just need to state it’s parallel,
and that the variable we’ll keep our data in is a shared variable that needs to be accessed in parallel.
Next, we define the steps for each parallel iteration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">counter_var</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">impact</span>:<span style="color:#bbb"> </span>${followers_count}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">fetch_reblogs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${prefix + &#34;/statuses/&#34; + status.id + &#34;/reblogged_by&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>reblogs<span style="color:#bbb">
</span></span></span></code></pre></div><p>Above, we get the list of people who reshared our post. And for each of these accounts,
we’re incrementing our impact counter with the number of their followers.
It’s another loop, but that doesn’t need to be done in parallel, as we’re not calling any API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">iterate_reblogs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">for</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">value</span>:<span style="color:#bbb"> </span>reblog<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span>${reblogs.body}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">increment_reblog</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">impact</span>:<span style="color:#bbb"> </span>${impact + reblog.followers_count}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">update_impact_map</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">impact_map[status.url]</span>:<span style="color:#bbb"> </span>${impact}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And we finish the workflow by returning the data:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">returnOutput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">id</span>:<span style="color:#bbb"> </span>${account_id}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">account</span>:<span style="color:#bbb"> </span>${account}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">server</span>:<span style="color:#bbb"> </span>${server}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">followers</span>:<span style="color:#bbb"> </span>${followers_count}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">impact</span>:<span style="color:#bbb"> </span>${impact_map}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This will return an output similar to this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;account&#34;</span>: <span style="color:#4070a0">&#34;glaforge&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;followers&#34;</span>: <span style="color:#40a070">878</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;id&#34;</span>: <span style="color:#4070a0">&#34;109314675907601286&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;impact&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109422399389341013&#34;</span>: <span style="color:#40a070">945</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109462120695384207&#34;</span>: <span style="color:#40a070">1523</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109494881278500194&#34;</span>: <span style="color:#40a070">121385</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109495686235916646&#34;</span>: <span style="color:#40a070">878</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109516968335141401&#34;</span>: <span style="color:#40a070">1002</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109523829424569844&#34;</span>: <span style="color:#40a070">878</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109528949144442301&#34;</span>: <span style="color:#40a070">896</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109620174916140649&#34;</span>: <span style="color:#40a070">1662</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;https://uwyn.net/@glaforge/109621803885287542&#34;</span>: <span style="color:#40a070">1523</span>,
</span></span><span style="display:flex;"><span>    <span style="">...</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;server&#34;</span>: <span style="color:#4070a0">&#34;uwyn.net&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>With this little workflow, I can check how my toots are doing on this new social media! As next steps,
you might want to check out how to get started with API orchestration with Google Cloud Workflows,
<a href="https://cloud.google.com/workflows/docs/create-workflow-console">in the cloud console</a>,
or <a href="https://cloud.google.com/workflows/docs/create-workflow-gcloud">from the command-line</a>.
And to go further, potentially, it might be interesting to
<a href="https://glaforge.dev/posts/2022/02/09/schedule-a-workflow-execution/">schedule a workflow execution</a>
with <a href="https://cloud.google.com/scheduler">Cloud Scheduler</a>.
We could also imagine storing those stats in a database (perhaps <a href="https://cloud.google.com/bigquery">BigQuery</a> for some analytics,
or simply <a href="https://cloud.google.com/firestore">Firestore</a>
or <a href="https://cloud.google.com/sql">CloudSQL</a>), to see how your impact evolves over time.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Workflows patterns and best practices — Part 3</title><link>https://glaforge.dev/posts/2022/12/06/workflows-patterns-and-best-practices-part-3/</link><pubDate>Tue, 06 Dec 2022 14:12:25 +0100</pubDate><guid>https://glaforge.dev/posts/2022/12/06/workflows-patterns-and-best-practices-part-3/</guid><description>&lt;p>This is a three-part series of posts, in which we summarize Workflows and service orchestration patterns. In this third and final post, we talk about managing workflow life cycles and the benefits of using Firestore with Workflows. &lt;/p>
&lt;h3 id="use-subworkflows-and-terraform-to-manage-workflow-definitions">Use subworkflows and Terraform to manage workflow definitions&lt;/h3>
&lt;p>If you&amp;rsquo;re not careful, the workflow definitions you create with YAML or JSON can get out of hand pretty quickly. While it is possible to use subworkflows to define snippets of a workflow that can be reused from multiple workflows, Workflows does not support importing these subworkflows. Thankfully, there are other tools, such as Terraform, that can help.&lt;/p></description><content:encoded>
<![CDATA[<p>This is a three-part series of posts, in which we summarize Workflows and service orchestration patterns. In this third and final post, we talk about managing workflow life cycles and the benefits of using Firestore with Workflows. </p>
<h3 id="use-subworkflows-and-terraform-to-manage-workflow-definitions">Use subworkflows and Terraform to manage workflow definitions</h3>
<p>If you&rsquo;re not careful, the workflow definitions you create with YAML or JSON can get out of hand pretty quickly. While it is possible to use subworkflows to define snippets of a workflow that can be reused from multiple workflows, Workflows does not support importing these subworkflows. Thankfully, there are other tools, such as Terraform, that can help.</p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/deploying-multi-yaml-workflows-definitions-terraform">Deploying multi-YAML Workflows definitions with Terraform</a> post, we showed how to use Terraform to define a workflow and import it into a Terraform configuration file. We further showed how to also import a subworkflow in the same workflow definition. This makes it easier to manage workflows and subworkflow definitions.</p>
<p>When you&rsquo;re defining a workflow, make sure you have a strategy to define and reuse subworkflows with Terraform or some other tool.</p>
<h3 id="gitops-your-service-orchestrations">GitOps your service orchestrations</h3>
<p><a href="https://opengitops.dev/">GitOps</a> takes DevOps best practices used for application development (such as version control and CI/CD) and applies them to infrastructure automation. Service orchestrations, which have their own definition files and deployment cycles, can benefit from a GitOps approach as well.</p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/gitsops-service-orchestration">GitOps your service orchestrations</a> post, we showed how to use Cloud Build to manage an automated and staged rollout of workflow changes with tests along the way to minimize risk.</p>
<p><figure>
  <a href="#img-602a2146370e39cb953514e05f02c59f">
    <img src="/img/workflows-patterns/GitOps_Blog_1.max-800x800.max-800x800.png"
      alt="/img/workflows-patterns/GitOps_Blog_1.max-800x800.max-800x800.png"
       />
  </a>
  <figcaption>/img/workflows-patterns/GitOps_Blog_1.max-800x800.max-800x800.png</figcaption>
</figure>
<div class="lightbox" id="img-602a2146370e39cb953514e05f02c59f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-patterns/GitOps_Blog_1.max-800x800.max-800x800.png"
    alt="/img/workflows-patterns/GitOps_Blog_1.max-800x800.max-800x800.png"
     />
  <div class="lightbox-caption">/img/workflows-patterns/GitOps_Blog_1.max-800x800.max-800x800.png</div>
</div>
</p>
<h3 id="plan-for-multi-environment-orchestrations">Plan for multi-environment orchestrations</h3>
<p>While GitOps helps to manage the deployment lifecycle of a workflow, sometimes you need to make changes to the workflow before deploying to different environments. That means you need to design workflows with multiple environments in mind. For example, instead of hardcoding the URLs called from the workflow, replace the URLs with staging and production URLs depending on where the workflow is being deployed.</p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/multi-environment-service-orchestrations">Multi-environment service orchestrations</a> post, we showed three different ways of replacing URLs in a workflow: passing URLs as runtime arguments, using Cloud Build to deploy multiple versions, and using Terraform to deploy multiple versions.</p>
<p><figure>
  <a href="#img-3376d963589d1cc1540a9cd57d698a31">
    <img src="/img/workflows-patterns/GOB2_-_2.max-700x700.max-700x700.png"
      alt="/img/workflows-patterns/GOB2_-_2.max-700x700.max-700x700.png"
       />
  </a>
  <figcaption>/img/workflows-patterns/GOB2_-_2.max-700x700.max-700x700.png</figcaption>
</figure>
<div class="lightbox" id="img-3376d963589d1cc1540a9cd57d698a31">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-patterns/GOB2_-_2.max-700x700.max-700x700.png"
    alt="/img/workflows-patterns/GOB2_-_2.max-700x700.max-700x700.png"
     />
  <div class="lightbox-caption">/img/workflows-patterns/GOB2_-_2.max-700x700.max-700x700.png</div>
</div>
</p>
<h3 id="manage-external-state-with-firestore">Manage external state with Firestore</h3>
<p>You define workflows YAML/JSON as the recipe and then execute it with optional runtime arguments as an individual isolated instance of that recipe. Sometimes, you need to store some state (typically a key/value pair) in a step from one workflow execution and later read that state in another step from another workflow execution. There&rsquo;s no intrinsic key/value store in Workflows. However, you can use Firestore to store and read key/value pairs from Workflows.</p>
<p>In our <a href="https://medium.com/google-cloud/worklows-state-management-with-firestore-99237f08c5c5">Workflows state management with Firestore</a> post and its associated <a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/state-management-firestore">sample</a>, we showed a couple of subworkflows to put and get key/value pairs from Workflows with Firestore. This pattern is very useful when you need to manage some state in your workflow. </p>
<h3 id="workflows-for-reliable-work-and-firestore-for-reactive-ui">Workflows for reliable work and Firestore for reactive UI</h3>
<p>You can count on Workflows to perform some long-running work reliably and as an admin, check the status of the workflow and the current running step using the Google Cloud console or the Workflows API. However, how do you keep end users up to date about the status of the workflow? To have this kind of reactive UI, you can have your workflow write its status to a Firestore document and have Firestore notify connected end users in real time.</p>
<p>At Google I/O, we demonstrated this pattern with two examples. In the <a href="https://github.com/GoogleCloudPlatform/smart-expenses">expense report application</a>, the UI updates the status of the approval process both for the employee and the manager. In the <a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/callback-translation">translation application using callbacks</a>, Firestore is used to show the status of an ongoing translation.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/l3aMs00ziYA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<hr />
<p>This wraps up our three-part series. For questions or feedback, or if you want to share your own best practices and patterns, feel free to reach out to us on Twitter @<a href="https://twitter.com/meteatamel">meteatamel</a> and @<a href="https://twitter.com/glaforge">glaforge</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Turning a Website Into a Desktop Application</title><link>https://glaforge.dev/posts/2022/11/29/turning-a-website-into-a-desktop-application/</link><pubDate>Tue, 29 Nov 2022 12:47:19 +0100</pubDate><guid>https://glaforge.dev/posts/2022/11/29/turning-a-website-into-a-desktop-application/</guid><description>&lt;p>Probably like most of you, my dear readers, I have too many browser windows open,
with tons of tabs for each window. But there are always apps I come back to very often,
like my email (professional &amp;amp; personal), my calendar, my chat app, or even social media
sites like Mastodon or Twitter. You can switch from window to window with &lt;code>CTRL/CMD-Tab&lt;/code>,
but you also have to move between tabs potentially. But for the most common webapps or
websites I’m using, I wanted to have a dedicated desktop application.&lt;/p></description><content:encoded>
<![CDATA[<p>Probably like most of you, my dear readers, I have too many browser windows open,
with tons of tabs for each window. But there are always apps I come back to very often,
like my email (professional &amp; personal), my calendar, my chat app, or even social media
sites like Mastodon or Twitter. You can switch from window to window with <code>CTRL/CMD-Tab</code>,
but you also have to move between tabs potentially. But for the most common webapps or
websites I’m using, I wanted to have a dedicated desktop application.</p>
<p>Initially, I was on the lookout for a Mac specific approach, as I’ve been a macOS users
for many years. So I found some Mac-specific apps that can handle that. This website mentions
<a href="https://www.makeuseof.com/tag/website-desktop-mac-app/">5 approaches for macOS</a>, including
free, freemium, non-free apps, like Fluid, Applicationize (creating a Chrome extension),
Web2Desk, or Unite. However, some of them create big hundred-mega wrappers. Another approach
on Macs was using Automator, to create a pop-up window, but that’s just a pop-up, not a real app.
There are also some promising open source projects like <a href="https://tauri.app/">Tauri</a>
and <a href="https://github.com/nativefier/nativefier">Nativefier</a> which seem promising.</p>
<p>Fortunately, there’s a cool feature from Chrome, that should work across all OSses,
and not just macOS. So if you’re on Linux or Windows, please read on.
The websites you’ll turn into applications don’t even need to be PWAs (Progressive Web Apps).</p>
<p>Here’s how to proceed:</p>
<p>First, navigate to your website you want to transform into an application with your Chrome browser.</p>
<p>Click on the triple dots in the top right corner, then <code>More Tools</code>, and finally <code>Create Shorctut</code>:</p>
<p><figure>
  <a href="#img-aaaf5799ac8b56adca92703c22ea6ec3">
    <img src="/img/chrome-to-app/web2app-01-create-shortcut.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-aaaf5799ac8b56adca92703c22ea6ec3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/chrome-to-app/web2app-01-create-shortcut.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>It will then let you customise the name of the application.
It’ll reuse the favicon of the website as icon for the application.
But be sure to check <code>Open as window</code> to create a standalone application:</p>
<p><figure>
  <a href="#img-2e3a81b0e456c9f0d87e97bc0d195e5c">
    <img src="/img/chrome-to-app/web2app-02-open-as-window.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-2e3a81b0e456c9f0d87e97bc0d195e5c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/chrome-to-app/web2app-02-open-as-window.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then you’ll be able to open the website as a standalone application:</p>
<p><figure>
  <a href="#img-7e2de5b268eb2ffd279624f802555e6f">
    <img src="/img/chrome-to-app/web2app-03-standalone-app.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-7e2de5b268eb2ffd279624f802555e6f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/chrome-to-app/web2app-03-standalone-app.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I was curious if a similar feature existed with other browsers like Firefox.
For the little fox, the only thing I could find was the ability to open Firefox in kiosk mode,
in full-screen. But I wanted a window I could dimension however I wanted, not necessarily full-screen.
I hope that Firefox will add that capability at some point.
But for now, I’m happy to have this solution with Chrome!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Workflows patterns and best practices — Part 2</title><link>https://glaforge.dev/posts/2022/11/28/workflows-patterns-and-best-practices-part-2/</link><pubDate>Mon, 28 Nov 2022 14:12:05 +0100</pubDate><guid>https://glaforge.dev/posts/2022/11/28/workflows-patterns-and-best-practices-part-2/</guid><description>&lt;p>This is part 2 of a three-part series of posts, in which we summarize Workflows and service orchestration patterns. You can apply these patterns to better take advantage of Workflows and service orchestration on Google Cloud.&lt;/p>
&lt;p>In the &lt;a href="https://glaforge.dev/posts/2022/11/22/workflows-patterns-and-best-practices-part-1/">first post&lt;/a>, we introduced some general tips and tricks, as well as patterns for event-driven orchestrations, parallel steps, and connectors. This second post covers more advanced patterns.  &lt;/p>
&lt;p>Let&amp;rsquo;s dive in!&lt;/p>
&lt;h2 id="design-for-resiliency-with-retries-and-the-saga-pattern">Design for resiliency with retries and the saga pattern&lt;/h2>
&lt;p>It&amp;rsquo;s easy to put together a workflow that chains a series of services,  especially if you assume that those services will never fail. This is a common distributed systems fallacy, however, because of course a service will fail at some point. The workflow step calling that service will fail, and then the whole workflow will fail. This is not what you want to see in a resilient architecture. Thankfully, Workflows has building blocks to handle both transient and permanent service failures. &lt;/p></description><content:encoded>
<![CDATA[<p>This is part 2 of a three-part series of posts, in which we summarize Workflows and service orchestration patterns. You can apply these patterns to better take advantage of Workflows and service orchestration on Google Cloud.</p>
<p>In the <a href="https://glaforge.dev/posts/2022/11/22/workflows-patterns-and-best-practices-part-1/">first post</a>, we introduced some general tips and tricks, as well as patterns for event-driven orchestrations, parallel steps, and connectors. This second post covers more advanced patterns.  </p>
<p>Let&rsquo;s dive in!</p>
<h2 id="design-for-resiliency-with-retries-and-the-saga-pattern">Design for resiliency with retries and the saga pattern</h2>
<p>It&rsquo;s easy to put together a workflow that chains a series of services,  especially if you assume that those services will never fail. This is a common distributed systems fallacy, however, because of course a service will fail at some point. The workflow step calling that service will fail, and then the whole workflow will fail. This is not what you want to see in a resilient architecture. Thankfully, Workflows has building blocks to handle both transient and permanent service failures. </p>
<p>In our post on <a href="https://cloud.google.com/blog/topics/developers-practitioners/implementing-saga-pattern-workflows">Implementing the saga pattern in Workflows</a> (and its associated e-commerce <a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/retries-and-saga">sample</a>), we talked about how you can apply the saga pattern and take compensation steps to undo one or more previous steps with the <code>try/except</code> block for permanent service failures. </p>
<p><figure>
  <a href="#img-23af8078ad0a535c65bc3ac2369ea22f">
    <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_2_1Fl6i9t.max-1400x1400.png"
      alt="https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_2_1Fl6i9t.max-1400x1400.png"
       />
  </a>
  <figcaption>https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_2_1Fl6i9t.max-1400x1400.png</figcaption>
</figure>
<div class="lightbox" id="img-23af8078ad0a535c65bc3ac2369ea22f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_2_1Fl6i9t.max-1400x1400.png"
    alt="https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_2_1Fl6i9t.max-1400x1400.png"
     />
  <div class="lightbox-caption">https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_2_1Fl6i9t.max-1400x1400.png</div>
</div>
</p>
<p><em>Saga pattern implementation</em></p>
<p>We also showed how to handle transient service failures by adding the default HTTP retry policy with the <code>try/retry</code> block of Workflows. If you have non-idempotent steps, you need to adjust the retry policy to <code>default_retry_non_idempotent</code>. In most cases, you need a custom retry policy with a longer backoff, because the default one waits at most ~8 seconds.  When eventual consistency over a longer time period is more important than fast failure, consider a longer retry policy over several minutes or even hours with a large multiplier is much more likely to succeed during temporary outages. </p>
<p>Don&rsquo;t take network calls for granted. Make sure you design your orchestration with resiliency in mind using retries and the saga pattern.</p>
<h2 id="wait-for-http-and-event-callbacks-instead-of-polling">Wait for HTTP and event callbacks instead of polling</h2>
<p>Sometimes, your orchestration might need to wait for a long-running job or an asynchronous event in another system before it can continue with the rest of the steps. The workflow can ask for that input by polling an endpoint or a queue, but this requires complicated polling logic, wasted polling cycles and most likely higher latency. A better approach is to use Workflows <a href="https://cloud.google.com/workflows/docs/creating-callback-endpoints">callbacks</a> to wait for HTTP calls or events.  </p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-workflows-callbacks">Introducing Workflows callbacks</a> post, we showed a workflow that waits for human input for automated machine learning translations. Similarly, the <a href="https://cloud.google.com/blog/topics/developers-practitioners/smarter-applications-document-ai-workflows-and-cloud-functions">Smarter applications with Document AI, Workflows and Cloud Functions</a> post shows a document processing workflow that waits for human approval of expense reports with a callback. </p>
<p><figure>
  <a href="#img-1c085c08ccf76872d7b2578d30b3891e">
    <img src="/img/workflows-patterns/architecture-diagram_6rwVYEP.max-1500x1500.png"
      alt="/img/workflows-patterns/architecture-diagram_6rwVYEP.max-1500x1500.png"
       />
  </a>
  <figcaption>/img/workflows-patterns/architecture-diagram_6rwVYEP.max-1500x1500.png</figcaption>
</figure>
<div class="lightbox" id="img-1c085c08ccf76872d7b2578d30b3891e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-patterns/architecture-diagram_6rwVYEP.max-1500x1500.png"
    alt="/img/workflows-patterns/architecture-diagram_6rwVYEP.max-1500x1500.png"
     />
  <div class="lightbox-caption">/img/workflows-patterns/architecture-diagram_6rwVYEP.max-1500x1500.png</div>
</div>
</p>
<p><em>Smarter applications with Document AI, Workflows and Cloud Functions</em></p>
<p>While both of these posts are focused on waiting for <em>HTTP callbacks</em>, in <a href="https://medium.com/google-cloud/creating-workflows-that-pause-and-wait-for-events-4da201741f2a">Creating Workflows that pause and wait for events</a> post, we showed how a workflow can also wait for Pub/Sub and Cloud Storage events. You can even use Google Sheets as a quick and simple frontend for human approvals as we showed in the <a href="https://medium.com/google-cloud/workflows-that-pause-and-wait-for-human-approvals-from-google-sheets-53673ced2a81">Workflows that pause and wait for human approvals from Google Sheets</a> post. </p>
<p>When designing a workflow, consider waiting for HTTP calls and events, rather than polling.  </p>
<h2 id="orchestrate-long-running-batch-jobs">Orchestrate long-running batch jobs</h2>
<p>If you need to execute long-running jobs, Google Cloud has services such as <a href="https://cloud.google.com/batch">Batch</a> and <a href="https://cloud.google.com/run/docs/create-jobs">Cloud Run jobs</a> that can help. While these services are great for completing  long-running jobs on Compute Engine instances and containers, you still need to create and manage the Batch and Cloud Run job service. One pattern that works really well is to use Workflows to manage these services running batch jobs. </p>
<p>In the <a href="https://cloud.google.com/blog/topics/developers-practitioners/taking-screenshots-web-pages-cloud-run-jobs-workflows-and-eventarc">Taking screenshots of web pages with Cloud Run jobs, Workflows, and Eventarc</a> post, we showed how Cloud Run jobs take screenshots of web pages and Workflows creates and manages parallel Cloud Run jobs tasks. Similarly, in the <a href="https://github.com/GoogleCloudPlatform/batch-samples/tree/main/primegen">Batch - prime number generator</a> sample, we showed how to run prime number generator containers in parallel on Compute Engine instances using Google Batch. The lifecycle of the Batch job is managed by Workflows.</p>
<p><figure>
  <a href="#img-29976dfe939059b215a382f2a57b392f">
    <img src="/img/workflows-patterns/image1_2.max-2200x2200.png"
      alt="/img/workflows-patterns/image1_2.max-2200x2200.png"
       />
  </a>
  <figcaption>/img/workflows-patterns/image1_2.max-2200x2200.png</figcaption>
</figure>
<div class="lightbox" id="img-29976dfe939059b215a382f2a57b392f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-patterns/image1_2.max-2200x2200.png"
    alt="/img/workflows-patterns/image1_2.max-2200x2200.png"
     />
  <div class="lightbox-caption">/img/workflows-patterns/image1_2.max-2200x2200.png</div>
</div>
</p>
<p><em>Take screenshots of webpages with Cloud Run jobs, Workflows and Eventarc</em></p>
<p>Use the right services for long-running batch jobs and use Workflows to manage their life cycles.</p>
<h2 id="treat-serverful-workloads-as-serverless-with-workflows">Treat serverful workloads as serverless with Workflows</h2>
<p>Sometimes, you really need a server due to some serverless limitation. For example, you might need to run on GPUs or execute a long-running process that lasts weeks or months. In those cases, Compute Engine can provide you with customized virtual machines (VM), but you&rsquo;re stuck with managing those VMs yourself. </p>
<p>In this kind of IT automation scenario, you can use Workflows to create VMs with the customizations you need, run the workloads for however long you need (Workflows executions can last up to one year), and return the result in the end.  This pattern enables you to use servers but manage them as if they were serverless services using Workflows.</p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/long-running-containers-workflows-and-compute-engine">Long-running containers with Workflows and Compute Engine</a> post, we showed how to use Workflows to spin up a VM, start a prime number generator on the VM, run it for however long you want, and return the result.</p>
<p>Next time you need to spin up a VM, treat it like a serverless service with Workflows. </p>
<h2 id="run-command-line-tools-with-workflows-and-cloud-build">Run command-line tools with Workflows and Cloud Build</h2>
<p>We often use command-line tools such as <code>gcloud</code> to manage Google Cloud resources or <code>kubectl</code> to manage Kubernetes clusters. Wouldn&rsquo;t it be nice if we could call these tools from Workflows and orchestrate management of resources?</p>
<p>In the <a href="https://medium.com/google-cloud/executing-commands-gcloud-kubectl-from-workflows-ad6b85eaf39c">Executing commands (gcloud, kubectl) from Workflows</a> post, we showed how to use Cloud Build to run these tools and how to create and call that Cloud Build step from Workflows using the Cloud Build connector. </p>
<p>Keep in mind that this pattern is not limited to <code>gcloud</code> and <code>kubectl</code>. Any tool you can run in a container can potentially be a target for Workflows with the help of Cloud Build.</p>
<p>Integrate command-line tools into your workflows when needed by calling a Cloud Build step from Workflows.</p>
<hr />
<p>This second part series covered a lot of ground, but there&rsquo;s still more to cover! We&rsquo;ll wrap up the series in our <a href="https://glaforge.dev/posts/2022/12/06/workflows-patterns-and-best-practices-part-3/">third and final post</a>, which describes how to manage the lifecycle of workflow definitions and the benefits of using Firestore. Stay tuned! For questions or feedback, feel free to reach out to us on Twitter @<a href="https://twitter.com/meteatamel">meteatamel</a> and @<a href="https://twitter.com/glaforge">glaforge</a>.</p>
<p><a href="https://cloud.google.com/blog/topics/developers-practitioners/workflows-patterns-and-best-practices-part-1"></a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Workflows patterns and best practices — Part 1</title><link>https://glaforge.dev/posts/2022/11/22/workflows-patterns-and-best-practices-part-1/</link><pubDate>Tue, 22 Nov 2022 14:11:45 +0100</pubDate><guid>https://glaforge.dev/posts/2022/11/22/workflows-patterns-and-best-practices-part-1/</guid><description>&lt;p>For the last couple of years, we&amp;rsquo;ve been using &lt;a href="https://cloud.google.com/workflows">Workflows&lt;/a>, Google Cloud&amp;rsquo;s service orchestrator, to bring order to our serverless microservices architectures. As we used and gained more experience with Workflows and service orchestration, we shared what he had learned in conference talks, blog posts, samples, and tutorials. Along the way, some common patterns and best practices emerged. &lt;/p>
&lt;p>To help you take better advantage of Workflows and service orchestration on Google Cloud, we&amp;rsquo;ve summarized these proven patterns and best practices in a three-part series of blog posts.&lt;/p></description><content:encoded>
<![CDATA[<p>For the last couple of years, we&rsquo;ve been using <a href="https://cloud.google.com/workflows">Workflows</a>, Google Cloud&rsquo;s service orchestrator, to bring order to our serverless microservices architectures. As we used and gained more experience with Workflows and service orchestration, we shared what he had learned in conference talks, blog posts, samples, and tutorials. Along the way, some common patterns and best practices emerged. </p>
<p>To help you take better advantage of Workflows and service orchestration on Google Cloud, we&rsquo;ve summarized these proven patterns and best practices in a three-part series of blog posts.</p>
<p>Let&rsquo;s get started with Part 1!</p>
<h2 id="make-a-conscious-choice-on-the-communication-style-upfront">Make a conscious choice on the communication style upfront</h2>
<p>Choosing a communication style is more of a task than a pattern, but it is an important one to complete before even considering service orchestration. </p>
<p>When you have multiple services, you need to decide how these services will communicate. The options are:</p>
<ul>
<li><strong>Direct</strong> service-to-service communication</li>
<li>Indirect <strong>event-driven</strong> communication (also known as choreography)</li>
<li>A central <strong>orchestrator</strong> (e.g. Workflows) directing the communication</li>
</ul>
<p>There&rsquo;s no right or wrong, only pros and cons. Direct service-to-service communication is easy to implement but creates tight coupling between services. Events enable loosely coupled services at the expense of harder monitoring and debugging when something goes wrong. An orchestrator, while less flexible, brings order to communication without the tight coupling of direct service-to-service communication and the chaos of events in choreographed architectures.</p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/orchestrating-pic-daily-serverless-app-workflows">Orchestrating the Pic-a-Daily serverless app with Workflows</a> post, we explained how we transformed an event-driven application into an orchestrated application and the benefits of doing so. In <a href="https://cloud.google.com/blog/topics/developers-practitioners/choosing-right-orchestrator-google-cloud">Choosing the right orchestrator in Google Cloud</a>, we talked about which service is best suited for different orchestration needs (scheduled, service, and data). </p>
<p>As you design your architecture, make a conscious choice on the communication style with pros and cons in mind, and if you choose to use orchestration, be sure to use the right orchestrator for the task. </p>
<h2 id="keep-these-tips-and-tricks-for-workflows-in-mind">Keep these tips and tricks for Workflows in mind</h2>
<p>Once you decide to use Workflows for service orchestration, you&rsquo;ll realize that Workflows has its own strengths and idiosyncrasies. Here are some general tips and tricks that we found useful as we used Workflows:</p>
<ul>
<li><strong>Avoid hard-coding URLs</strong> for more portable workflows across environments.</li>
<li><strong>Use substeps</strong> to collect a common series of steps in one logical unit.</li>
<li><strong>Wrap string expressions</strong> to avoid parsing problems.</li>
<li><strong>Replace logic-less services with declarative API calls</strong> to avoid boilerplate code.</li>
<li><strong>Store what you need, free what you can</strong> to keep memory consumption under control.</li>
<li><strong>Use subworkflows and call external workflows</strong> to increase reuse.</li>
</ul>
<p>Check our <a href="https://glaforge.appspot.com/article/workflows-tips-n-tricks">post on Workflows tips and tricks</a> for a more detailed explanation of these points. </p>
<h3 id="consider-event-driven-orchestration">Consider event-driven orchestration</h3>
<p>The choice on the communication style is not all or nothing. You can and should combine different styles when it makes sense. For example, there is a common pattern where services that are closely related are managed by an orchestrator like Workflows but that orchestration is triggered by an event from a service like Eventarc. Similarly, we see architectures where the end of an orchestration is a Pub/Sub message to some other orchestration or service. </p>
<p>In our <a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-eventarc-triggers-workflows">Introducing Eventarc triggers for Workflows</a> post, we showed how easy it is to route events to Workflows using Eventarc. In the <a href="https://youtu.be/2SI12QE-2DU">Build an event-driven orchestration with Eventarc and Workflows</a> video and its associated <a href="https://codelabs.developers.google.com/codelabs/cloud-event-driven-orchestration?hl=en#8">codelab</a> and <a href="https://github.com/GoogleCloudPlatform/eventarc-samples/tree/main/processing-pipelines/image-v3">sample</a>, we showed how to design an image processing pipeline where the services are managed by Workflows but the orchestration is triggered in a loosely coupled way by a Cloud Storage event via Eventarc:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/2SI12QE-2DU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Mix communication styles to get the best of both worlds: use orchestration when you need tight coupling between services but loose coupling with other orchestrations via events.</p>
<h2 id="use-connectors-when-you-can">Use connectors when you can</h2>
<p>Workflows has a rich set of <a href="https://cloud.google.com/workflows/docs/connectors">connectors</a> to call other Google Cloud services. They handle the formatting of requests for you, providing methods and arguments so that you don&rsquo;t need to get deep into the gory details of a Google Cloud API. More importantly, connectors enable a workflow to transparently wait for long-running Cloud API calls. This saves you from the tedious work of iterating and waiting for calls to complete; connectors take care of this for you! </p>
<p>In our <a href="https://cloud.google.com/workflows/docs/connectors">Introducing new connectors for Workflows</a> post, we showed you how Compute Engine connector simplified creating and stopping a virtual machine. </p>
<p>Whenever you want to call a Google Cloud API from Workflows, check to see if there&rsquo;s a connector for it. You&rsquo;ll be glad that you did and you can always request a new connector <a href="https://docs.google.com/forms/d/e/1FAIpQLScdDRwFHcFuy28hvjGq0XCBMyiVuhHGq2c-7Gy_no1ZuqKAOg/viewform?resourcekey=0-0BRW58uzC8wnVntuOiM7AQ">here</a>, if there&rsquo;s no connector.</p>
<h2 id="parallelize-when-you-can">Parallelize when you can</h2>
<p>When we talk about Workflows, we often talk about steps executed one after another sequentially. While Workflows is fast enough to run steps sequentially with no noticeable delay, not all steps need to run sequentially. Independent steps can actually run in parallel, and in some cases this can provide a significant speed up for workflow executions.</p>
<p>In the <a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-parallel-steps-workflows-speed-up-workflow-executions-by-running-steps-concurrently">Introducing Parallel Steps for Workflows</a> post and its associated video, we showed how running BigQuery jobs from Workflows in parallel can speed up the workflow execution by five times. The more independent steps you have, the more you can run those steps in parallel and the faster your workflow execution will be, especially with long-running tasks like BigQuery jobs.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/C1Reg1u1MXY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Try to keep your steps independent and make sure to take advantage of parallel steps when you can.</p>
<hr />
<p>This initial list of patterns and tips will help you get started taking better advantage of Workflows. We cover more advanced patterns in <a href="https://glaforge.dev/posts/2022/11/28/workflows-patterns-and-best-practices-part-2/">part 2</a> of this series. For questions or feedback, feel free to reach out to us on Twitter @<a href="https://twitter.com/meteatamel">meteatamel</a> and @<a href="https://twitter.com/glaforge">glaforge</a>.</p>
<p><a href="https://cloud.google.com/blog/topics/developers-practitioners/orchestrating-pic-daily-serverless-app-workflows"></a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>APIs, we have a Problem JSON</title><link>https://glaforge.dev/posts/2022/11/14/apis-we-have-a-problem-json/</link><pubDate>Mon, 14 Nov 2022 13:37:09 +0100</pubDate><guid>https://glaforge.dev/posts/2022/11/14/apis-we-have-a-problem-json/</guid><description>&lt;p>When designing a web API, not only do you have to think about the happy path when everything is alright,
but you also have to handle all the error cases: Is the payload received correct? Is there a typo in a field?
Do you need more context about the problem that occured?&lt;/p>
&lt;p>There’s only a limited set of status codes that can convey the kind of error you’re getting,
but sometimes you need to explain more clearly what the error is about.&lt;/p></description><content:encoded>
<![CDATA[<p>When designing a web API, not only do you have to think about the happy path when everything is alright,
but you also have to handle all the error cases: Is the payload received correct? Is there a typo in a field?
Do you need more context about the problem that occured?</p>
<p>There’s only a limited set of status codes that can convey the kind of error you’re getting,
but sometimes you need to explain more clearly what the error is about.</p>
<p>In the past, the APIs I was designing used to follow a common JSON structure for my error messages: a simple JSON object,
usually with a <code>message</code> field, and sometimes with extra info like a custom error <code>code</code>, or a <code>details</code> field that contained a longer explanation in plain English.
However, it was my own convention, and it’s not necessarily one that is used by others, or understood by tools that interact with my API.</p>
<p>So that’s why today, for reporting problems with my web APIs, I tend to use <strong>Problem JSON</strong>. This is actually an RFC (<a href="https://datatracker.ietf.org/doc/html/rfc7807">RFC-7807</a>)
whose title is “Problem Details for HTTP APIs”. Exactly what I needed, a specification for my error messages!</p>
<p>First of all, it’s a JSON content-type. Your API should specify the content-type with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="">Content-Type: application/problem+json
</span></span></span></code></pre></div><p>Content-types that end with <code>+json</code> are basically treated as <code>application/json</code>.</p>
<p>Now, an example payload from the specification looks like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">HTTP</span><span style="color:#666">/</span><span style="color:#40a070">1.1</span> <span style="color:#40a070">403</span> <span style="color:#007020">Forbidden</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#666">:</span> application/problem+json
</span></span><span style="display:flex;"><span>Content-Language<span style="color:#666">:</span> en
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>   <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;https://example.com/probs/out-of-credit&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;You do not have enough credit.&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#062873;font-weight:bold">&#34;detail&#34;</span>: <span style="color:#4070a0">&#34;Your current balance is 30, but that costs 50.&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#062873;font-weight:bold">&#34;instance&#34;</span>: <span style="color:#4070a0">&#34;/account/12345/msgs/abc&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#062873;font-weight:bold">&#34;balance&#34;</span>: <span style="color:#40a070">30</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#062873;font-weight:bold">&#34;accounts&#34;</span>: [<span style="color:#4070a0">&#34;/account/12345&#34;</span>, <span style="color:#4070a0">&#34;/account/67890&#34;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>There are some standard fields like:</p>
<ul>
<li><strong>type</strong>: a URI reference that uniquely identifies the problem type</li>
<li><strong>title</strong>: a short readable error statement</li>
<li><strong>status</strong>: the original HTTP status code from the origin server</li>
<li><strong>detail</strong>: a longer text explanation of the issue</li>
<li><strong>instance</strong>: a URI that points to the resource that has issues</li>
</ul>
<p>Then, in the example above, you also have custom fields: <code>balance</code> and <code>accounts</code>, which are specific to your application,
and not part of the specification. Which means you can expand the Problem JSON payload to include details that are specific to your application.</p>
<p>Note: Although I’m only covering JSON APIs, the RFC also suggests an <code>application/problem+xml</code> alternative for the XML APIs.</p>
<h2 id="icing-on-the-cake-built-in-support-in-micronaut">Icing on the cake: built-in support in Micronaut</h2>
<p>My framework of choice these days for all my apps is <a href="https://micronaut.io/">Micronaut</a>, as it’s super fast and memory efficient.
And it’s only recently that I realized there was actually a <a href="https://micronaut-projects.github.io/micronaut-problem-json/2.5.2/guide/index.html">Micronaut extension for Problem JSON</a>!
So instead of returning a custom JSON payload manually, I can use the built-in integration.</p>
<p>Here’s an example from the Problem JSON Micronaut extension:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/product&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">ProductController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#555;font-weight:bold">@Get</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#555;font-weight:bold">@Status</span>(HttpStatus.<span style="color:#4070a0">OK</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">index</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">throw</span><span style="color:#bbb"> </span>Problem.<span style="color:#4070a0">builder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">withType</span>(URI.<span style="color:#4070a0">create</span>(<span style="color:#4070a0">&#34;https://example.org/out-of-stock&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">withTitle</span>(<span style="color:#4070a0">&#34;Out of Stock&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">withStatus</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>HttpStatusType(HttpStatus.<span style="color:#4070a0">BAD_REQUEST</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">withDetail</span>(<span style="color:#4070a0">&#34;Item B00027Y5QG is no longer available&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">with</span>(<span style="color:#4070a0">&#34;product&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;B00027Y5QG&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>.<span style="color:#4070a0">build</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Which will return a JSON error as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;status&#34;</span>: <span style="color:#40a070">400</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;Out of Stock&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;detail&#34;</span>: <span style="color:#4070a0">&#34;Item B00027Y5QG is no longer available&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;https://example.org/out-of-stock&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;parameters&#34;</span>: {<span style="color:#062873;font-weight:bold">&#34;product&#34;</span>: <span style="color:#4070a0">&#34;B00027Y5QG&#34;</span>}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now, I’m happy that I can use some official standard for giving more details about the errors returned by my APIs!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Workflows Tips and Tricks</title><link>https://glaforge.dev/posts/2022/11/04/workflows-tips-and-tricks/</link><pubDate>Fri, 04 Nov 2022 14:15:43 +0100</pubDate><guid>https://glaforge.dev/posts/2022/11/04/workflows-tips-and-tricks/</guid><description>&lt;p>Here are some general tips and tricks that we found useful as we used Google Cloud &lt;a href="https://cloud.google.com/workflows">Workflows&lt;/a>:&lt;/p>
&lt;h2 id="avoid-hard-coding-urls">Avoid hard-coding URLs&lt;/h2>
&lt;p>Since Workflows is all about calling APIs and service URLs, it&amp;rsquo;s important to have some clean way to handle those URLs.
You can hard-code them in your workflow definition, but the problem is that your workflow can become harder to maintain.
In particular, what happens when you work with multiple environments?
You have to duplicate your YAML definitions and use different URLs for the prod vs staging vs dev environments.
It is error-prone and quickly becomes painful to make modifications to essentially the same workflow in multiple files.
To avoid hard-coding those URLs, there are a few approaches.&lt;/p></description><content:encoded>
<![CDATA[<p>Here are some general tips and tricks that we found useful as we used Google Cloud <a href="https://cloud.google.com/workflows">Workflows</a>:</p>
<h2 id="avoid-hard-coding-urls">Avoid hard-coding URLs</h2>
<p>Since Workflows is all about calling APIs and service URLs, it&rsquo;s important to have some clean way to handle those URLs.
You can hard-code them in your workflow definition, but the problem is that your workflow can become harder to maintain.
In particular, what happens when you work with multiple environments?
You have to duplicate your YAML definitions and use different URLs for the prod vs staging vs dev environments.
It is error-prone and quickly becomes painful to make modifications to essentially the same workflow in multiple files.
To avoid hard-coding those URLs, there are a few approaches.</p>
<p>The first one is to externalize those URLs, and
<a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/multi-env-deployment#option-1-use-urls-as-runtime-arguments">pass them as workflow execution arguments</a>.
This is great for workflow executions that are launched via the CLI, via the various client libraries, or the REST &amp; gRPC APIs.
However, there&rsquo;s a limitation to this first approach, in the case of event-triggered workflows, where the invoker is Eventarc.
In that case, that&rsquo;s Eventarc that decides which arguments to pass (ie. the event payload).
There&rsquo;s no way to pass extra arguments in that case.</p>
<p>A safer approach is then to use some placeholder replacement techniques.
Just use a tool that replaces some specific string tokens in your definition file, before deploying that updated definition.
We explored that approach using some
<a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/multi-env-deployment#option-2-use-cloud-build-to-deploy-multiple-versions">Cloud Build steps that do some string replacement</a>.
You still have one single workflow definition file, but you deploy variants for the different environments.
If you&rsquo;re using Terraform for provisioning your infrastructure, we&rsquo;ve got you covered, you can also employ a
<a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/multi-env-deployment#option-3-use-terraform-to-deploy-multiple-versions">similar technique with Terraform</a>.</p>
<p>There are also other possible approaches, like taking advantage of Secret Manager and the dedicated
<a href="https://cloud.google.com/workflows/docs/reference/googleapis/secretmanager/Overview">workflow connector</a>,
to store those URLs, and retrieve them. Or you can also
<a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/gcs-read-write-json#load-environment-specific-variables-from-a-json-file-in-gcs">read some JSON file in a cloud storage bucket</a>,
within which you would store those environment specific details.</p>
<h2 id="take-advantage-of-sub-steps">Take advantage of sub-steps</h2>
<p>Apart from branching or looping, defining your steps is a pretty sequential process. One step happens after another.
Steps are their own atomic operation.
However, often, some steps really go hand-in-hand, like making an API call, logging its outcome, retrieving and assigning parts of the payload into some variables.
You can actually regroup common steps into substeps. This becomes handy when you are branching from a set of steps to another set of steps, without having to point at the right atomic step.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">callWikipedia</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">checkSearchTermInInput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb">  </span>${&#34;searchTerm&#34;  in  input}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>- <span style="color:#062873;font-weight:bold">searchTerm</span>:<span style="color:#bbb">  </span>${input.searchTerm}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>readWikipedia<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">getCurrentTime</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>currentDateTime<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">setFromCallResult</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>- <span style="color:#062873;font-weight:bold">searchTerm</span>:<span style="color:#bbb">  </span>${currentDateTime.body.dayOfTheWeek}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">readWikipedia</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://en.wikipedia.org/w/api.php<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#062873;font-weight:bold">action</span>:<span style="color:#bbb">  </span>opensearch<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#062873;font-weight:bold">search</span>:<span style="color:#bbb">  </span>${searchTerm}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>wikiResult<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">returnOutput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${wikiResult.body[1]}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="wrap-expressions">Wrap expressions</h2>
<p>The dollar/curly brace <code>${}</code> expressions are not part of the YAML specification, so what you put inside sometimes doesn&rsquo;t play well with YAML&rsquo;s expectations.
For example, putting a colon inside a string inside an expression can be problematic, as the YAML parser believes the colon is the end of the YAML key, and the start of the right-hand-side.
So to be safe, you can actually wrap your expressions within quotes, like: <code>'${...}'</code></p>
<p>Expressions can span several lines, as well as the strings within that expression.
That&rsquo;s handy for SQL queries for BigQuery, like in our <a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/bigquery-parallel">example</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">  </span>${<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;SELECT TITLE, SUM(views)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    FROM `bigquery-samples.wikipedia_pageviews.&#34;</span><span style="color:#bbb">  </span>+  table  +  &#34;`<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>WHERE LENGTH(TITLE) &gt; 10<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>GROUP BY TITLE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>ORDER BY SUM(VIEWS) DESC<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>LIMIT 100&#34;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="replace-logic-less-services-with-declarative-api-calls">Replace logic-less services with declarative API calls</h2>
<p>In our <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/">serverless workshop</a>, in lab 1,
we had a <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/blob/master/functions/image-analysis/nodejs/index.js#L19">function service</a>
that was making a call to the Cloud Vision API, checking a boolean attribute, then writing the result in Firestore.
But the Vision API can be called declaratively from Workflows.
The boolean check can be done with a switch conditional expression, and even writing to Firestore can be done via a declarative API call.
When rewriting our application in <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/blob/master/workflows/workflows.yaml#L33">lab 6</a>
to use the orchestrated approach, we moved those logic-less calls into declarative API calls.</p>
<p>There are times where Workflows lack some built-in function that you would need, so you have no choice but fork into a function to do the job.
But when you have pretty logic-less code that just makes some API calls, you&rsquo;d better just write this declaratively using Workflows syntax.</p>
<p>It doesn&rsquo;t mean that everything, or as much as possible, should be done declaratively in a Workflow either.
Workflows is not a hammer, and it&rsquo;s definitely not a programming language.
So when there&rsquo;s real logic, you definitely need to call some service that represents that business logic.</p>
<h2 id="store-what-you-need-free-what-you-can">Store what you need, free what you can</h2>
<p>Workflows keeps on granting more memory to workflow executions, but there are times, with big API response payloads, where you&rsquo;d be happy to have even more memory.
That&rsquo;s when sane memory management can be a good thing to do.
You can be selective in what you store into variables: don&rsquo;t store too much, but store just the right payload part you really need.
Once you know you won&rsquo;t need the content of one of your variables, you can also reassign that variable to null, that should also free that memory.
Also, in the first place, if the APIs allow you to filter the result more aggressively, you should also do that.
Last but not least, if you&rsquo;re calling a service that returns a gigantic payload that can&rsquo;t fit in Workflows memory,
you could always delegate that call to your own function that would take care of making the call on your behalf, and returning to you just the parts you&rsquo;re really interested in.</p>
<p>Don&rsquo;t forget to check the documentation on <a href="https://cloud.google.com/workflows/quotas">quotas and limits</a> to know more about what&rsquo;s possible.</p>
<h2 id="take-advantage-of-sub-workflows-and-the-ability-to-call-external-workflows">Take advantage of sub-workflows and the ability to call external workflows</h2>
<p>In your workflows, sometimes there are some steps that you might need to repeat.
That&rsquo;s when <a href="https://cloud.google.com/workflows/docs/reference/syntax/subworkflows">subworkflows</a> become handy.
Sub-workflows are like sub-routines, procedures or methods.
They are a way to make a set of steps reusable in several places of your workflow, potentially parameterized with different arguments.
The sole downside maybe is that subworkflows are just local to your workflow definition, so they can&rsquo;t be reused in other workflows.
In that case, you could actually create a dedicated reusable workflow, because you can also
<a href="https://cloud.google.com/workflows/docs/reference/googleapis/workflowexecutions/Overview">call workflows from other workflows</a>! The workflows connector for workflows is there to help.</p>
<h2 id="summary">Summary</h2>
<p>We&rsquo;ve covered a few tips and tricks, and we&rsquo;ve reviewed some useful advice on how to make the best use of Workflows.
There are certainly others we&rsquo;re forgetting about.
So feel free to share them with <a href="https://twitter.com/meteatamel">@meteatamel</a> and <a href="https://twitter.com/glaforge">@glaforge</a> over Twitter.</p>
<p>And don&rsquo;t forget to double check what&rsquo;s in the Workflows <a href="https://cloud.google.com/workflows/docs">documentation</a>.
In particular, have a look at the built-in functions of the <a href="https://cloud.google.com/workflows/docs/reference/stdlib/overview">standard library</a>,
at the <a href="https://cloud.google.com/workflows/docs/reference/googleapis">list of connectors</a> that you can use,
and perhaps even print the <a href="https://cloud.google.com/workflows/docs/reference/syntax/syntax-cheat-sheet">syntax cheat sheet</a>!</p>
<p>Lastly, check out all the <a href="https://cloud.google.com/workflows/docs/samples">samples</a> in the documentation portal,
and all the <a href="https://github.com/GoogleCloudPlatform/workflows-demos">workflow demos</a> Mete and I have built and open sourced over time.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Retrieve YouTube views count with youtubeDL, JQ, and a Docker container</title><link>https://glaforge.dev/posts/2022/10/26/retrieve-youtube-views-count-with-youtubedl-jq-and-a-docker-container/</link><pubDate>Wed, 26 Oct 2022 14:53:39 +0100</pubDate><guid>https://glaforge.dev/posts/2022/10/26/retrieve-youtube-views-count-with-youtubedl-jq-and-a-docker-container/</guid><description>&lt;p>I wanted to track the number of views, and also likes, of some YouTube videos I was featured in.
For example, when I present a talk at a conference, often the video becomes available at a later time, and I&amp;rsquo;m not the owner of the channel or video.
At first, I wanted to use the &lt;a href="https://developers.google.com/youtube/v3">YouTube Data API&lt;/a>,
but I had the impression that I could only see the stats of videos or channels I own,
however I think I might be wrong, and should probably revisit this approach later on.&lt;/p></description><content:encoded>
<![CDATA[<p>I wanted to track the number of views, and also likes, of some YouTube videos I was featured in.
For example, when I present a talk at a conference, often the video becomes available at a later time, and I&rsquo;m not the owner of the channel or video.
At first, I wanted to use the <a href="https://developers.google.com/youtube/v3">YouTube Data API</a>,
but I had the impression that I could only see the stats of videos or channels I own,
however I think I might be wrong, and should probably revisit this approach later on.</p>
<p>My first intuition was to just scrape the web page of the video, but it&rsquo;s a gobbledygook of JavaScript,
and I couldn&rsquo;t really find an easy way to consistently get the numbers in that sea of compressed JavaScript.
That&rsquo;s when I remembered about the <a href="https://youtube-dl.org/">youtube-dl project</a>.
Some people think of this project as a way to download videos to watch offline, but it&rsquo;s also a useful tool that offers lots of metadata about the videos.
You can actually even use the project without downloading videos at all, but just fetching the metadata.</p>
<p>For example, if I want to get the video metadata, without downloading, I can launch the following command, after having installed the tool locally:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>youtube-dl -j -s https://www.youtube.com/watch?v<span style="color:#666">=</span>xJi6pldZnsw
</span></span></code></pre></div><p>The <code>-s</code> flag is equivalent to <code>--simulate</code> which doesn&rsquo;t download anything on disk.</p>
<p>And the <code>-j</code> flag is the short version of <code>--dump-json</code> which returns a big JSON file with lots of metadata, including the view count,
but also things like links to transcripts in various languages, chapters, creator, duration, episode number, and so on and so forth.</p>
<p>Now, I&rsquo;m only interested in view counts, likes, dislikes.
So I&rsquo;m using <a href="https://stedolan.github.io/jq/">jq</a> to filter the big JSON payload, and create a resulting JSON document with just the fields I want.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>jq <span style="color:#4070a0">&#39;{&#34;id&#34;:.id,&#34;title&#34;:.title,&#34;views&#34;:.view_count,&#34;likes&#34;:(.like_count // 0), &#34;dislikes&#34;:(.dislike_count // 0)}&#39;</span>
</span></span></code></pre></div><p>This long command is creating a JSON structure as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;id&#34;</span>: <span style="color:#4070a0">&#34;xJi6pldZnsw&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;title&#34;</span>: <span style="color:#4070a0">&#34;Reuse old smartphones to monitor 3D prints, with WebRTC, WebSockets and Serverless by G. Laforge&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;views&#34;</span>: <span style="color:#40a070">172</span>,
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;likes&#34;</span>: <span style="color:#40a070">6</span>,
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;dislikes&#34;</span>: <span style="color:#40a070">0</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>.id</code>, <code>.title</code>, <code>.view_count</code>, etc, are searching for that particular key in the big JSON documentation.
The <code>// 0</code> notation is to avoid null values and return 0 if there&rsquo;s no key or if the value associated with the key is null.
So I always get a number &mdash; although I noticed that sometimes, the likes are not always properly accounted for, but I haven&rsquo;t figured out why.</p>
<p>So far so good&hellip; but if you pass a URL of a video with a playlist, or if you pass a playlist URL, it will fetch all the metadata for all the videos.
This is actually useful: you can even create your own playlists for the videos you want to track.
There&rsquo;s one odd thing happening though when using youtube-dl with such URLs: it will output a JSON document per line for each video.
It&rsquo;s not returning an array of those documents. So I found a nice trick with jq to always put the results within an array, whether you pass a URL for a single video, or a video with a playlist:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>​​jq -n <span style="color:#4070a0">&#39;[inputs]&#39;</span>
</span></span></code></pre></div><p>So I&rsquo;m piping the <code>youtube-dl</code> command, the first and second <code>jq</code> commands.</p>
<p>Rather than installing those tools locally, I decided to containerize my magic commands.</p>
<p>Let me first show you the whole <code>Dockerfile</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> ubuntu:latest</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">RUN</span> apt-get update <span style="color:#666">&amp;&amp;</span> apt-get -y install wget<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span>    <span style="color:#666">&amp;&amp;</span> wget https://yt-dl.org/latest/youtube-dl -O /usr/local/bin/youtube-dl<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span>    <span style="color:#666">&amp;&amp;</span> chmod a+rx /usr/local/bin/youtube-dl<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span>    <span style="color:#666">&amp;&amp;</span> apt-get -y install python3-pip jq <span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span>    <span style="color:#666">&amp;&amp;</span> pip3 install --upgrade youtube-dl<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">COPY</span> ./launch-yt-dl.sh<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">RUN</span> chmod +x /launch-yt-dl.sh<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ENTRYPOINT</span> [<span style="color:#4070a0">&#34;./launch-yt-dl.sh&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>And also this bash script mentioned in the Dockerfile:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">#!/bin/bash\
</span></span></span><span style="display:flex;"><span><span style="color:#007020"></span>youtube-dl -j -s -- <span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$@</span><span style="color:#4070a0">&#34;</span> | jq <span style="color:#4070a0">&#39;{&#34;id&#34;:.id,&#34;title&#34;:.title,&#34;views&#34;:.view_count,&#34;likes&#34;:(.like_count // 0), &#34;dislikes&#34;:(.dislike_count // 0)}&#39;</span> | jq -n <span style="color:#4070a0">&#39;[inputs]&#39;</span>
</span></span></code></pre></div><p>I went with the latest ubuntu image. I ran some apt-get commands to install wget to download the latest youtube-dl release, Python 3&rsquo;s pip to upgrade youtube-dl.
There&rsquo;s no recent apt module for youtube-dl, hence why we have those steps together.</p>
<p>What&rsquo;s more interesting is why I don&rsquo;t have the youtube-dl and jq commands in the Dockerfile directly, but instead in a dedicated bash script.
Initially I had an <code>ENTRYPOINT</code> that pointed at youtube-dl, so that arguments passed to the docker run command would be passed as arguments of that entrypoint.
However, after those commands, I still have to pipe with my jq commands. And I couldn&rsquo;t find how to do so with <code>ENTRYPOINT</code> and <code>CMD</code>.
When raising the problem on <a href="https://twitter.com/glaforge/status/1584800385256280064">twitter</a>,
my friends <a href="https://twitter.com/glours/status/1584810960136683521">Guillaume Lours</a> and <a href="https://twitter.com/cfurmaniak/status/1584845647647506432">Christophe Furmaniak</a> pointed me in the right direction with this idea of passing through a script.</p>
<p>So I use the <code>$@</code> bash shortcut, which expands as arguments <code>$1 $2 $3</code>, etc. in case there are several videos passed as arguments.
I have the jq pipes after that shortcut.
But for my <code>ENTRYPOINT</code>, it&rsquo;s fine, the args are passed directly to it, and it&rsquo;s that intermediary script that weaves the args in my longer command.</p>
<p>Next, I just need to build my Docker container:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker build . -t yt-video-stat
</span></span></code></pre></div><p>And then run it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker run --rm -it yt-video-stat <span style="color:#4070a0">&#34;https://www.youtube.com/watch?v=xJi6pldZnsw&#34;</span>
</span></span></code></pre></div><p>And voila, I have the stats for the YouTube videos I&rsquo;m interested in!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Build and deploy Java 17 apps on Cloud Run with Cloud Native Buildpacks on Temurin</title><link>https://glaforge.dev/posts/2022/10/24/build-deploy-java-17-apps-on-cloud-run-with-cloud-native-buildpacks-on-temurin/</link><pubDate>Mon, 24 Oct 2022 11:33:25 +0100</pubDate><guid>https://glaforge.dev/posts/2022/10/24/build-deploy-java-17-apps-on-cloud-run-with-cloud-native-buildpacks-on-temurin/</guid><description>&lt;p>In this article, let&amp;rsquo;s revisit the topic of deploying Java apps on &lt;a href="https://cloud.run/">Cloud Run&lt;/a>.
In particular, I&amp;rsquo;ll deploy a &lt;a href="https://micronaut.io/">Micronaut&lt;/a> app, written with &lt;a href="https://jdk.java.net/17/">Java 17&lt;/a>, and built with &lt;a href="https://gradle.org/">Gradle&lt;/a>.&lt;/p>
&lt;h2 id="with-a-custom-dockerfile">With a custom Dockerfile&lt;/h2>
&lt;p>On Cloud Run, you deploy containerised applications, so you have to decide the way you want to build a container for your application.
In a &lt;a href="https://glaforge.dev/posts/2020/03/24/start-the-fun-with-java-14-and-micronaut-inside-serverless-containers-on-cloud-run/">previous article&lt;/a>,
I showed an example of using your own Dockerfile, which would look as follows with an OpenJDK 17, and enabling preview features of the language:&lt;/p></description><content:encoded>
<![CDATA[<p>In this article, let&rsquo;s revisit the topic of deploying Java apps on <a href="https://cloud.run/">Cloud Run</a>.
In particular, I&rsquo;ll deploy a <a href="https://micronaut.io/">Micronaut</a> app, written with <a href="https://jdk.java.net/17/">Java 17</a>, and built with <a href="https://gradle.org/">Gradle</a>.</p>
<h2 id="with-a-custom-dockerfile">With a custom Dockerfile</h2>
<p>On Cloud Run, you deploy containerised applications, so you have to decide the way you want to build a container for your application.
In a <a href="https://glaforge.dev/posts/2020/03/24/start-the-fun-with-java-14-and-micronaut-inside-serverless-containers-on-cloud-run/">previous article</a>,
I showed an example of using your own Dockerfile, which would look as follows with an OpenJDK 17, and enabling preview features of the language:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> openjdk:17</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">WORKDIR</span><span style="color:#4070a0"> /app</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">COPY</span> ./ ./<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">RUN</span> ./gradlew shadowJar<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">EXPOSE</span><span style="color:#4070a0">  8080</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">CMD</span> [<span style="color:#4070a0">&#34;java&#34;</span>, <span style="color:#4070a0">&#34;--enable-preview&#34;</span>, <span style="color:#4070a0">&#34;-jar&#34;</span>, <span style="color:#4070a0">&#34;build/libs/app-0.1-all.jar&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>To further improve on that Dockerfile, you could use a multistage Docker build to first build the app in one step with Gradle, and then run it in a second step.
Also you might want to parameterize the command as the JAR file name is hard-coded.</p>
<p>To build the image, you can build it locally with Docker, and then push it to Container Registry, and then deploy it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># gcloud auth configure-docker</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># gcloud components install docker-credential-gcr</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>docker build . --tag gcr.io/YOUR_PROJECT_ID/IMAGE_NAME
</span></span><span style="display:flex;"><span>docker push gcr.io/YOUR_PROJECT_ID/IMAGE_NAME
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>gcloud run deploy weekend-service <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --image gcr.io/YOUR_PROJECT_ID/IMAGE_NAME
</span></span></code></pre></div><p>Instead of building locally with Docker, you could also let <a href="https://cloud.google.com/build">Cloud Build</a> do it for you:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud builds submit . --tag gcr.io/YOUR_PROJECT_ID/SERVICE_NAME
</span></span></code></pre></div><h2 id="with-jib">With JIB</h2>
<p>Instead of messing around with Dockerfiles, you can also let <a href="https://github.com/GoogleContainerTools/jib">JIB</a> create the container for you,
like I wrote in <a href="https://glaforge.dev/posts/2020/08/04/running-micronaut-serverlessly-on-google-cloud-platform/">another article</a>.
You configure Gradle to use the JIB plugin:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>plugins <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">...</span>
</span></span><span style="display:flex;"><span>id <span style="color:#4070a0">&#34;com.google.cloud.tools.jib&#34;</span> version <span style="color:#4070a0">&#34;2.8.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">...</span>
</span></span><span style="display:flex;"><span>tasks <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    jib <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        from <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            image <span style="color:#666">=</span> <span style="color:#4070a0">&#34;gcr.io/distroless/java17-debian11&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>        to <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            image <span style="color:#666">=</span> <span style="color:#4070a0">&#34;gcr.io/YOUR_PROJECT_ID/SERVICE_NAME&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>You specify the version of the plugin, but you also indicate that you want to use Java 17 by choosing a base image with that same version.
Be sure to change the placeholders for your project ID and service name.
Feel free to lookup the <a href="https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin">documentation</a> about the JIB Gradle plugin.
You can then let Gradle build the container with ./gradlew jib, or with ./gradlew jibDockerBuild if you want to use your local Docker daemon.</p>
<h2 id="with-cloud-native-buildpacks">With Cloud Native Buildpacks</h2>
<p>Now that we covered the other approaches, let&rsquo;s zoom in on using <a href="https://buildpacks.io/">Cloud Native Buildpacks</a> instead, in particular,
the <a href="https://github.com/GoogleCloudPlatform/buildpacks">Google Cloud Native Buildpacks</a>.
With buildpacks, you don&rsquo;t have to bother with Dockerfiles or with building the container before deploying the service.
You let Cloud Run use buildpacks to build, containerize, and deploy your application from sources.</p>
<p>Out of the box, the buildpack actually targets Java 8 or Java 11.
But I&rsquo;m interested in running the latest LTS version of Java, with Java 17, to take advantage of some preview features like records, sealed classes, switch expressions, etc.</p>
<p>In my Gradle build, I specify that I&rsquo;m using Java 17, but also enable preview features:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>java <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    toolchain <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        languageVersion<span style="color:#666">.</span><span style="color:#4070a0">set</span><span style="color:#666">(</span>JavaLanguageVersion<span style="color:#666">.</span><span style="color:#4070a0">of</span><span style="color:#666">(</span><span style="color:#40a070">17</span><span style="color:#666">))</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Like in Cédric Champeaus&rsquo;s <a href="https://melix.github.io/blog/2020/06/java-feature-previews-gradle.html">blog post</a>, to enable preview features,
you should also tell Gradle you want to enable them for compilation, test, and execution tasks:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>tasks<span style="color:#666">.</span><span style="color:#4070a0">withType</span><span style="color:#666">(</span>JavaCompile<span style="color:#666">).</span><span style="color:#4070a0">configureEach</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span> options<span style="color:#666">.</span><span style="color:#4070a0">compilerArgs</span><span style="color:#666">.</span><span style="color:#4070a0">add</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;--enable-preview&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tasks<span style="color:#666">.</span><span style="color:#4070a0">withType</span><span style="color:#666">(</span>Test<span style="color:#666">).</span><span style="color:#4070a0">configureEach</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    useJUnitPlatform<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    jvmArgs<span style="color:#666">(</span><span style="color:#4070a0">&#34;--enable-preview&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tasks<span style="color:#666">.</span><span style="color:#4070a0">withType</span><span style="color:#666">(</span>JavaExec<span style="color:#666">).</span><span style="color:#4070a0">configureEach</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    jvmArgs<span style="color:#666">(</span><span style="color:#4070a0">&#34;--enable-preview&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>So far so good, but as I said, the default native buildpack isn&rsquo;t using Java 17, and I want to specify that I use preview features.
So when I tried to deploy my Cloud Run app from sources with the buildpack, simply by running the gcloud deploy command, I would get an error.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud beta run deploy SERVICE_NAME
</span></span></code></pre></div><p>To circumvent this problem, I had to add a configuration file, to instruct the buildpack to use Java 17. I created a project.toml file at the root of my project:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[[build.env]]
</span></span><span style="display:flex;"><span>name = <span style="color:#4070a0">&#34;GOOGLE_RUNTIME_VERSION&#34;</span>
</span></span><span style="display:flex;"><span>value = <span style="color:#4070a0">&#34;17&#34;</span>
</span></span><span style="display:flex;"><span>[[build.env]]
</span></span><span style="display:flex;"><span>name = <span style="color:#4070a0">&#34;GOOGLE_ENTRYPOINT&#34;</span>
</span></span><span style="display:flex;"><span>value = <span style="color:#4070a0">&#34;java --enable-preview -jar /workspace/build/libs/app-0.1-all.jar&#34;</span>
</span></span></code></pre></div><p>I specify that the runtime version must use Java 17. But I also add the &ndash;enable-preview flag to enable the preview features at runtime.</p>
<h2 id="adoptium-temuring-openjdk-17">Adoptium Temuring OpenJDK 17</h2>
<p>The icing on the cake is that the build is using <a href="https://adoptium.net/">Adoptium</a>&rsquo;s <a href="https://adoptium.net/temurin/releases/">Temurin</a> build of OpenJDK 17,
as we recently <a href="https://blog.adoptium.net/2022/10/adoptium-welcomes-google/">announced</a>!
If you look at the build logs in Cloud Build, you should see some output mentioning it, like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;link&#34;</span>: <span style="color:#4070a0">&#34;https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.4.1%2B1/OpenJDK17U-jdk-sources_17.0.4.1_1.tar.gz&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;OpenJDK17U-jdk-sources_17.0.4.1_1.tar.gz&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&#34;size&#34;</span>: <span style="color:#40a070">105784017</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Way to go! Java 17 Micronaut app, deployed on Temurin on Cloud Run thanks to cloud native buildpacks! I win at buzzword bingo 🙂</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Choreography vs orchestration in microservices and best practices</title><link>https://glaforge.dev/talks/2022/10/20/choreography-vs-orchestration-in-microservices-and-best-practices/</link><pubDate>Thu, 20 Oct 2022 21:56:33 +0100</pubDate><guid>https://glaforge.dev/talks/2022/10/20/choreography-vs-orchestration-in-microservices-and-best-practices/</guid><description>&lt;p>We went from a single monolith to a set of microservices that are small, lightweight, and easy to implement. Microservices enable reusability, make it easier to change and scale apps on demand but they also introduce new problems. How do microservices interact with each other toward a common goal? How do you figure out what went wrong when a business process composed of several microservices fails? Should there be a central orchestrator controlling all interactions between services or should each service work independently, in a loosely coupled way, and only interact through shared events? In this talk, we’ll explore the Choreography vs Orchestration question and see demos of some of the tools that can help. And we&amp;rsquo;ll explore some best practices and patterns to apply when adopting an orchestration approach.&lt;/p></description><content:encoded>
<![CDATA[<p>We went from a single monolith to a set of microservices that are small, lightweight, and easy to implement. Microservices enable reusability, make it easier to change and scale apps on demand but they also introduce new problems. How do microservices interact with each other toward a common goal? How do you figure out what went wrong when a business process composed of several microservices fails? Should there be a central orchestrator controlling all interactions between services or should each service work independently, in a loosely coupled way, and only interact through shared events? In this talk, we’ll explore the Choreography vs Orchestration question and see demos of some of the tools that can help. And we&rsquo;ll explore some best practices and patterns to apply when adopting an orchestration approach.</p>
<script async class="speakerdeck-embed" data-id="328a826a55f447f7907db099c0aa03ab" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Reuse old smartphones to monitor 3D prints with WebRTC WebSockets and serverless</title><link>https://glaforge.dev/talks/2022/10/13/reuse-old-smartphones-to-monitor-3d-prints-with-webrtc-websockets-and-serverless/</link><pubDate>Thu, 13 Oct 2022 13:59:44 +0100</pubDate><guid>https://glaforge.dev/talks/2022/10/13/reuse-old-smartphones-to-monitor-3d-prints-with-webrtc-websockets-and-serverless/</guid><description>&lt;p>Reuse old smartphones to monitor 3D prints, with WebRTC, WebSockets and Serverless
Monitoring my 3D prints in my basement means climbing lots of stairs back and forth! So here’s my story about how I reused an old smartphone to check the status of my prints. I built a small web app that uses WebRTC to exchange video streams between my broadcasting smartphone and viewers, with WebSockets for signaling, and a serverless platform for easily deploying and hosting my containerized app.&lt;/p></description><content:encoded>
<![CDATA[<p>Reuse old smartphones to monitor 3D prints, with WebRTC, WebSockets and Serverless
Monitoring my 3D prints in my basement means climbing lots of stairs back and forth! So here’s my story about how I reused an old smartphone to check the status of my prints. I built a small web app that uses WebRTC to exchange video streams between my broadcasting smartphone and viewers, with WebSockets for signaling, and a serverless platform for easily deploying and hosting my containerized app.</p>
<script async class="speakerdeck-embed" data-id="3e5dfb35f64c48d9ba4a211122711b7c" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Cloud Run service in Go calling a Workflows callback endpoint</title><link>https://glaforge.dev/posts/2022/09/27/a-cloud-run-service-in-go-calling-a-workflows-callback-endpoint/</link><pubDate>Tue, 27 Sep 2022 16:02:00 +0100</pubDate><guid>https://glaforge.dev/posts/2022/09/27/a-cloud-run-service-in-go-calling-a-workflows-callback-endpoint/</guid><description>&lt;p>It&amp;rsquo;s all &lt;a href="https://seroter.com/">Richard Seroter&lt;/a>&amp;rsquo;s fault, I ended up dabbling with &lt;a href="https://go.dev/">Golang&lt;/a>!
We were chatting about a use case using Google Cloud &lt;a href="https://cloud.google.com/workflows">Workflows&lt;/a> and a &lt;a href="https://cloud.run/">Cloud Run&lt;/a> service implemented in Go.
So it was the occasion to play a bit with Go. Well, I still don&amp;rsquo;t like error handling&amp;hellip; But let&amp;rsquo;s rewind the story a bit!&lt;/p>
&lt;p>Workflows is a fully-managed service/API orchestrator on Google Cloud. You can create some advanced business workflows using YAML syntax.
I&amp;rsquo;ve built numerous little projects using it, and &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-workflows-callbacks">blogged&lt;/a> about it.
I particularly like its ability to pause a workflow execution, creating a &lt;a href="https://cloud.google.com/workflows/docs/creating-callback-endpoints">callback endpoint&lt;/a>
that you can call from an external system to resume the execution of the workflow.
With callbacks, you&amp;rsquo;re able to implement human validation steps, for example in an expense report application
where a manager validates or rejects an expense from someone in their team (this is what I implemented in this
&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/smarter-applications-document-ai-workflows-and-cloud-functions">article&lt;/a>).&lt;/p></description><content:encoded>
<![CDATA[<p>It&rsquo;s all <a href="https://seroter.com/">Richard Seroter</a>&rsquo;s fault, I ended up dabbling with <a href="https://go.dev/">Golang</a>!
We were chatting about a use case using Google Cloud <a href="https://cloud.google.com/workflows">Workflows</a> and a <a href="https://cloud.run/">Cloud Run</a> service implemented in Go.
So it was the occasion to play a bit with Go. Well, I still don&rsquo;t like error handling&hellip; But let&rsquo;s rewind the story a bit!</p>
<p>Workflows is a fully-managed service/API orchestrator on Google Cloud. You can create some advanced business workflows using YAML syntax.
I&rsquo;ve built numerous little projects using it, and <a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-workflows-callbacks">blogged</a> about it.
I particularly like its ability to pause a workflow execution, creating a <a href="https://cloud.google.com/workflows/docs/creating-callback-endpoints">callback endpoint</a>
that you can call from an external system to resume the execution of the workflow.
With callbacks, you&rsquo;re able to implement human validation steps, for example in an expense report application
where a manager validates or rejects an expense from someone in their team (this is what I implemented in this
<a href="https://cloud.google.com/blog/topics/developers-practitioners/smarter-applications-document-ai-workflows-and-cloud-functions">article</a>).</p>
<p>For my use case with Richard, we had a workflow that was creating such a callback endpoint.
This endpoint is called from a Cloud Run service implemented in Go.
Let&rsquo;s see how to implement the workflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">create_callback</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>events.create_callback_endpoint<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">http_callback_method</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;POST&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>callback_details<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">log_callback_creation</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>${&#34;Callback created, awaiting calls on &#34; + callback_details.url}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">await_callback</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>events.await_callback<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">callback</span>:<span style="color:#bbb"> </span>${callback_details}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span><span style="color:#40a070">86400</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>callback_request<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">log_callback_received</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">json</span>:<span style="color:#bbb"> </span>${callback_request.http_request}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_callback_request</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${callback_request.http_request}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The above workflow definition creates a callback endpoint. The URL of the callback endpoint is returned by that first step.
Then the workflow is waiting for the callback endpoint to be called externally.
The execution then resumes and logs some info about the incoming call and returns.</p>
<p>I deployed that workflow with a service account that has the Workflows Editor role, the Log Writer role (to log information),
and the Service Account Token Creator role (to create OAuth2 tokens),
as <a href="https://cloud.google.com/workflows/docs/creating-callback-endpoints#oauth-token">explained</a> in the documentation.</p>
<p>Now let&rsquo;s look at the Go service. I did a go mod init to create a new project.
I created a main.go source file with the following content:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> (
</span></span><span style="display:flex;"><span>	metadata <span style="color:#4070a0">&#34;cloud.google.com/go/compute/metadata&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#4070a0">&#34;encoding/json&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#4070a0">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#4070a0">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#4070a0">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#4070a0">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#4070a0">&#34;strings&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The metadata module is used to fetch an OAuth2 token from the <a href="https://cloud.google.com/run/docs/container-contract#metadata-server">Cloud Run metadata server</a>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// OAuth2 JSON struct</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">type</span> OAuth2TokenInfo <span style="color:#007020;font-weight:bold">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#60a0b0;font-style:italic">// defining struct variables</span>
</span></span><span style="display:flex;"><span>	Token      <span style="color:#902000">string</span> <span style="color:#4070a0">`json:&#34;access_token&#34;`</span>
</span></span><span style="display:flex;"><span>	TokenType  <span style="color:#902000">string</span> <span style="color:#4070a0">`json:&#34;token_type&#34;`</span>
</span></span><span style="display:flex;"><span>	Expiration <span style="color:#902000">uint32</span> <span style="color:#4070a0">`json:&#34;expires_in&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The metadata information in <code>instance/service-accounts/default/token</code> returns a JSON document that we map with the above struct.
We&rsquo;re interested in the access_token field, that we use further down to make the authenticated call to the Workflows callback endpoint.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">main</span>() {
</span></span><span style="display:flex;"><span>	log.<span style="color:#06287e">Print</span>(<span style="color:#4070a0">&#34;Starting server...&#34;</span>)
</span></span><span style="display:flex;"><span>	http.<span style="color:#06287e">HandleFunc</span>(<span style="color:#4070a0">&#34;/&#34;</span>, handler)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#60a0b0;font-style:italic">// Determine port for HTTP service.	port := os.Getenv(&#34;PORT&#34;)	</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">if</span> port <span style="color:#666">==</span> <span style="color:#4070a0">&#34;&#34;</span> {		
</span></span><span style="display:flex;"><span>        port = <span style="color:#4070a0">&#34;8080&#34;</span>		
</span></span><span style="display:flex;"><span>        log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;Defaulting to port %s&#34;</span>, port)	
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#60a0b0;font-style:italic">// Start HTTP server.	</span>
</span></span><span style="display:flex;"><span>    log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;Listening on port %s&#34;</span>, port)	
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">if</span> err <span style="color:#666">:=</span> http.<span style="color:#06287e">ListenAndServe</span>(<span style="color:#4070a0">&#34;:&#34;</span><span style="color:#666">+</span>port, <span style="color:#007020;font-weight:bold">nil</span>); err <span style="color:#666">!=</span> <span style="color:#007020;font-weight:bold">nil</span> {		
</span></span><span style="display:flex;"><span>        log.<span style="color:#06287e">Fatal</span>(err)	
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>main()</code> function starts our Go service. Let&rsquo;s now see the <code>handler()</code> function in more detail:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">func</span> <span style="color:#06287e">handler</span>(w http.ResponseWriter, r <span style="color:#666">*</span>http.Request) {
</span></span><span style="display:flex;"><span>	callbackUrl <span style="color:#666">:=</span> r.URL.<span style="color:#06287e">Query</span>().<span style="color:#06287e">Get</span>(<span style="color:#4070a0">&#34;callback_url&#34;</span>)
</span></span><span style="display:flex;"><span>	log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;Callback URL: %s&#34;</span>, callbackUrl)
</span></span></code></pre></div><p>We retrieve the <code>?callback_url</code> query parameter that will contain our callback endpoint URL.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#60a0b0;font-style:italic">// Fetch an OAuth2 access token from the metadata server</span>
</span></span><span style="display:flex;"><span>	oauthToken, errAuth <span style="color:#666">:=</span> metadata.<span style="color:#06287e">Get</span>(<span style="color:#4070a0">&#34;instance/service-accounts/default/token&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#007020;font-weight:bold">if</span> errAuth <span style="color:#666">!=</span> <span style="color:#007020;font-weight:bold">nil</span> {
</span></span><span style="display:flex;"><span>		log.<span style="color:#06287e">Fatal</span>(errAuth)
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div><p>Above, we make a call to the metadata server thanks to the metadata Go module.
And then we unmarshall the returned JSON document in our previously defined struct, with the following code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	data <span style="color:#666">:=</span> OAuth2TokenInfo{}
</span></span><span style="display:flex;"><span>	errJson <span style="color:#666">:=</span> json.<span style="color:#06287e">Unmarshal</span>([]<span style="color:#007020">byte</span>(oauthToken), <span style="color:#666">&amp;</span>data)
</span></span><span style="display:flex;"><span>	<span style="color:#007020;font-weight:bold">if</span> errJson <span style="color:#666">!=</span> <span style="color:#007020;font-weight:bold">nil</span> {
</span></span><span style="display:flex;"><span>		fmt.<span style="color:#06287e">Println</span>(errJson.<span style="color:#06287e">Error</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;OAuth2 token: %s&#34;</span>, data.Token)
</span></span></code></pre></div><p>Now it&rsquo;s time to prepare the call to our workflow callback endpoint, with a POST request:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	workflowReq, errWorkflowReq <span style="color:#666">:=</span> http.<span style="color:#06287e">NewRequest</span>(<span style="color:#4070a0">&#34;POST&#34;</span>, callbackUrl, strings.<span style="color:#06287e">NewReader</span>(<span style="color:#4070a0">&#34;{}&#34;</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#007020;font-weight:bold">if</span> errWorkflowReq <span style="color:#666">!=</span> <span style="color:#007020;font-weight:bold">nil</span> {
</span></span><span style="display:flex;"><span>		fmt.<span style="color:#06287e">Println</span>(errWorkflowReq.<span style="color:#06287e">Error</span>())
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div><p>We add the OAuth2 token as a bearer authorization via headers:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	workflowReq.Header.<span style="color:#06287e">Add</span>(<span style="color:#4070a0">&#34;authorization&#34;</span>, <span style="color:#4070a0">&#34;Bearer &#34;</span><span style="color:#666">+</span>data.Token)
</span></span><span style="display:flex;"><span>	workflowReq.Header.<span style="color:#06287e">Add</span>(<span style="color:#4070a0">&#34;accept&#34;</span>, <span style="color:#4070a0">&#34;application/json&#34;</span>)
</span></span><span style="display:flex;"><span>	workflowReq.Header.<span style="color:#06287e">Add</span>(<span style="color:#4070a0">&#34;content-type&#34;</span>, <span style="color:#4070a0">&#34;application/json&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    client <span style="color:#666">:=</span> <span style="color:#666">&amp;</span>http.Client{}	
</span></span><span style="display:flex;"><span>    workflowResp, workflowErr <span style="color:#666">:=</span> client.<span style="color:#06287e">Do</span>(workflowReq)	
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">if</span> workflowErr <span style="color:#666">!=</span> <span style="color:#007020;font-weight:bold">nil</span> {
</span></span><span style="display:flex;"><span>        fmt.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;Error making callback request: %s\n&#34;</span>, workflowErr)	
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    log.<span style="color:#06287e">Printf</span>(<span style="color:#4070a0">&#34;Status code: %d&#34;</span>, workflowResp.StatusCode)
</span></span><span style="display:flex;"><span>	fmt.<span style="color:#06287e">Fprintf</span>(w, <span style="color:#4070a0">&#34;Workflow callback called. Status code: %d&#34;</span>, workflowResp.StatusCode)}
</span></span></code></pre></div><p>We simply return the status code at the end of our Go service.</p>
<p>To deploy the Go service, I simply used the source deployment approach, by running gcloud run deploy,
and answering some questions (service name, region deployment, etc.)
After a couple of minutes, the service is up and running.</p>
<p>I create a new execution of the workflow from the Google Cloud console.
Once it&rsquo;s started, it logs the callback endpoint URL.
I copy its value, then I&rsquo;m calling my Cloud Run service with the <code>?callback_url=</code> query string pointing at that URL.
And voilà, the service resumes the execution of the workflow, and the workflow finishes.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Monitoring Website Changes with Workflows Cloud Functions and Sendgrid</title><link>https://glaforge.dev/posts/2022/09/12/monitoring-website-change-wwith-workflows-cloud-functions-and-sendgrid/</link><pubDate>Mon, 12 Sep 2022 15:53:44 +0100</pubDate><guid>https://glaforge.dev/posts/2022/09/12/monitoring-website-change-wwith-workflows-cloud-functions-and-sendgrid/</guid><description>&lt;p>Every year in France, around mid-September, there&amp;rsquo;s a special weekend where everyone can visit some famous places, usually closed the rest of the year. That&amp;rsquo;s &amp;ldquo;&lt;a href="https://journeesdupatrimoine.culture.gouv.fr/">Journée du Patrimoine&lt;/a>&amp;rdquo;. For example, you can visit places like the &lt;a href="https://www.elysee.fr/">Elysée Palace&lt;/a> or the &lt;a href="https://www.gouvernement.fr/le-patrimoine-de-l-hotel-de-matignon">Matignon Palace&lt;/a>, if you want to see where the French president, or the French prime minister work. However, for some of those places, it&amp;rsquo;s tricky to register online to book a slot, as there&amp;rsquo;s always a high demand for them. Furthermore, you have to be there at the right time to register, and often, you don&amp;rsquo;t even know when that day or time is! So I thought I could monitor the website of the Elysée Palace to see when the registration would open, by tracking changes on the Elysée website.&lt;/p></description><content:encoded>
<![CDATA[<p>Every year in France, around mid-September, there&rsquo;s a special weekend where everyone can visit some famous places, usually closed the rest of the year. That&rsquo;s &ldquo;<a href="https://journeesdupatrimoine.culture.gouv.fr/">Journée du Patrimoine</a>&rdquo;. For example, you can visit places like the <a href="https://www.elysee.fr/">Elysée Palace</a> or the <a href="https://www.gouvernement.fr/le-patrimoine-de-l-hotel-de-matignon">Matignon Palace</a>, if you want to see where the French president, or the French prime minister work. However, for some of those places, it&rsquo;s tricky to register online to book a slot, as there&rsquo;s always a high demand for them. Furthermore, you have to be there at the right time to register, and often, you don&rsquo;t even know when that day or time is! So I thought I could monitor the website of the Elysée Palace to see when the registration would open, by tracking changes on the Elysée website.</p>
<p>To monitor web page or website changes, there are a ton of online services available. There are often some limitations to the number of free requests, or to the frequency of the change checks. Being a developer on Google Cloud, I decided to write a simple solution that would take advantage of various Google Cloud services, namely:</p>
<ul>
<li><a href="https://cloud.google.com/workflows">Workflows</a>: to define the various steps of my site change workflow,</li>
<li><a href="https://cloud.google.com/scheduler">Cloud Scheduler</a>: to call execute my workflow on a regular basis,</li>
<li><a href="https://cloud.google.com/functions">Cloud Functions</a>: to compute a hash of the webpage, to see if the page changed,</li>
<li><a href="https://cloud.google.com/storage">Cloud Storage</a>: to store the hashes,</li>
<li><a href="https://sendgrid.com/">SendGrid</a> (not a Google Cloud product): to send me an email when changes have appeared,</li>
<li><a href="https://cloud.google.com/secret-manager">Secret Manager</a>: to store my SendGrid API key securely.</li>
</ul>
<p>Let&rsquo;s have a look first at a function that computes the hash of a webpage. As there&rsquo;s no hash function in the Workflows standard library, I decided to use a function to do that job. I used the Node.js runtime, with the crypto module, which contains a sha1 implementation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> crypto <span style="color:#666">=</span> require(<span style="color:#4070a0">&#39;crypto&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>exports.checksum <span style="color:#666">=</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> webpageBody <span style="color:#666">=</span> req.body.webpage;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> shasum <span style="color:#666">=</span> crypto.createHash(<span style="color:#4070a0">&#39;sha1&#39;</span>);
</span></span><span style="display:flex;"><span>  shasum.update(webpageBody);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> sha1 <span style="color:#666">=</span> shasum.digest(<span style="color:#4070a0">&#39;hex&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  res.status(<span style="color:#40a070">200</span>).send({sha1<span style="color:#666">:</span> sha1});
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The function receives the web page content from the workflow. Then I create the sha1 hash with that content, and return it in hexadecimal form, in a small JSON payload.</p>
<p>I created a Google Cloud Storage bucket to contain my web page hashes:</p>
<p><figure>
  <a href="#img-ae15e196b7e42832b8226d82cf901d5d">
    <img src="/img/patrimoine/patrimoine-gcs.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ae15e196b7e42832b8226d82cf901d5d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/patrimoine/patrimoine-gcs.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Since I&rsquo;m using SendGrid to notify me by email on changes, I store the API key securely in Secret Manager:</p>
<p><figure>
  <a href="#img-6eccdf4e1e358dc00106162f3b41b26d">
    <img src="/img/patrimoine/patrimoine-secret.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6eccdf4e1e358dc00106162f3b41b26d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/patrimoine/patrimoine-secret.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Now let&rsquo;s zoom on our workflow, piece by piece.<br />
First, I define some variables, like the name of my bucket, the name of my hashes text file, and I retrieve my SendGrid API key (see this previous <a href="https://glaforge.dev/posts/2022/02/04/using-the-secret-manager-connector-for-workflows-to-call-an-authenticated-service/">article about using Secret Manager with Workflows</a>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">assignment</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">bucket</span>:<span style="color:#bbb"> </span>hash_results<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">file_name</span>:<span style="color:#bbb"> </span>hash.txt<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">get_email_api_key</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.secretmanager.v1.projects.secrets.versions.accessString<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">secret_id</span>:<span style="color:#bbb"> </span>SENDGRID_API_KEY<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>EMAIL_API_KEY<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then I read the content of the previous hash in GCS (you can also check this article on how to <a href="https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/">read and write JSON data to a file in a bucket from a workflow</a>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">read_hash_from_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${&#34;https://storage.googleapis.com/download/storage/v1/b/&#34; + bucket + &#34;/o/&#34; + file_name}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">alt</span>:<span style="color:#bbb"> </span>media<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>hash_from_gcs<span style="color:#bbb">
</span></span></span></code></pre></div><p>It&rsquo;s time to make a simple HTTP GET call to the website. Currently, the URL is hard-coded, but we could parameterize the workflow to get that URL from the workflow execution input parameters instead.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">retrieve_web_page</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">           </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>https://evenements.elysee.fr/<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>web_page<span style="color:#bbb">
</span></span></span></code></pre></div><p>Once I retrieved the content of the URL (the result of that request is stored in the web_page variable), I can compute my hash, by calling my cloud function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">compute_hash</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>https://europe-west1-patrimoine-check.cloudfunctions.net/checksum<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">webpage</span>:<span style="color:#bbb"> </span>${web_page.body}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>hash_result<span style="color:#bbb">
</span></span></span></code></pre></div><p>That&rsquo;s where we introduce some branching in the workflow. If the web page hasn&rsquo;t changed we finish early, but if it has changed, then we&rsquo;re going to store the new hash in GCS:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">assign_hashes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">old_hash</span>:<span style="color:#bbb"> </span>${hash_from_gcs.body.sha1}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">new_hash</span>:<span style="color:#bbb"> </span>${hash_result.body.sha1}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">hash_msg</span>:<span style="color:#bbb"> </span>${&#34;Old hash = &#34; + old_hash + &#34; / New hash = &#34; + new_hash}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">conditionalSwitch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb"> </span>${new_hash != old_hash}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>write_hash_to_gcs<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>returnOutput<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">write_hash_to_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${&#34;https://storage.googleapis.com/upload/storage/v1/b/&#34; + bucket + &#34;/o&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>${file_name}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">sha1</span>:<span style="color:#bbb"> </span>${hash_result.body.sha1}<span style="color:#bbb">
</span></span></span></code></pre></div><p>I log the fact the website has changed, and I&rsquo;m calling the SendGrid API (like in this article on using SendGrid for sending emails from Workflows):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">site_changed_log</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>Website has changed<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">notify_by_email</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>https://api.sendgrid.com/v3/mail/send<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">headers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">Content-Type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">Authorization</span>:<span style="color:#bbb"> </span>${&#34;Bearer &#34; + EMAIL_API_KEY}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">personalizations</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>- <span style="color:#062873;font-weight:bold">to</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span>- <span style="color:#062873;font-weight:bold">email</span>:<span style="color:#bbb"> </span>me@gmail.com<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">from</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#062873;font-weight:bold">email</span>:<span style="color:#bbb"> </span>you@gmail.com<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">subject</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Elysée, page mise à jour&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>- <span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>text/plain<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                      </span><span style="color:#062873;font-weight:bold">value</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;La page de l&#39;Élysée a été mise à jour&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">log_hashes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb"> </span>${hash_msg}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">returnOutput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${hash_msg}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The workflows need to be invoked at a regular interval of time. Workflows can be configured to be invoked on a schedule via Cloud Scheduler (again, check this article on scheduling workflow executions). I configured my workflow to be triggered every minute, with the * * * * * cron pattern.<br />
<figure>
  <a href="#img-dee38a3adf490f0d1fbca33d9a146a7f">
    <img src="/img/patrimoine/patrimoine-schedule.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-dee38a3adf490f0d1fbca33d9a146a7f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/patrimoine/patrimoine-schedule.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And voila! I have my little workflow being invoked every minute to check if a web page has changed, and send me an email if so!</p>
<p>To be honest with you, the workflow worked perfectly&hellip; but the true story is that I wasn&rsquo;t monitoring the right URL, I should have monitored the front page instead. Furthermore, the page I was monitoring included some dynamic JavaScript code, but the HTML fetched wasn&rsquo;t really changing. I missed the registration window, and all the slots filled super rapidly before I even had the time to register my family for a visit! Shame on me, better check my URL next time, or create webpage screenshots with a headless Chrome running in Cloud Run or in Cloud Functions! Or, of course, use online services that have solved those problems with their years of experience! Hopefully, next year, I won&rsquo;t miss the registration! But it was fun to glue together all those useful services from Google Cloud, to solve a concrete problem.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Some beans and gems, some snakes and elephants, with Java 17, Ruby 3, Python 3.10, and PHP 8.1 in App Engine and Cloud Functions</title><link>https://glaforge.dev/posts/2022/04/14/some-beans-and-gems-some-snakes-and-elephants-with-java-17-ruby-3-python-310-and-php-81-in-app-engine-and-cloud-functions/</link><pubDate>Thu, 14 Apr 2022 14:03:46 +0100</pubDate><guid>https://glaforge.dev/posts/2022/04/14/some-beans-and-gems-some-snakes-and-elephants-with-java-17-ruby-3-python-310-and-php-81-in-app-engine-and-cloud-functions/</guid><description>&lt;p>Time to spill the beans and show the gems, to our friendly snakes and elephants: we&amp;rsquo;ve got some great news for Java, Ruby, Python and PHP serverless developers today. Google App Engine and Cloud Functions are adding new modern runtimes, allowing you to update to the major version release trains of those programming languages.&lt;/p>
&lt;p>In short, here&amp;rsquo;s what&amp;rsquo;s new:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Access to App Engine legacy bundled services for Java 11/17, Python 3 and Go 1.12+ runtimes, is &lt;strong>Generally Available&lt;/strong>&lt;/p></description><content:encoded>
<![CDATA[<p>Time to spill the beans and show the gems, to our friendly snakes and elephants: we&rsquo;ve got some great news for Java, Ruby, Python and PHP serverless developers today. Google App Engine and Cloud Functions are adding new modern runtimes, allowing you to update to the major version release trains of those programming languages.</p>
<p>In short, here&rsquo;s what&rsquo;s new:</p>
<ul>
<li>
<p>Access to App Engine legacy bundled services for Java 11/17, Python 3 and Go 1.12+ runtimes, is <strong>Generally Available</strong></p>
</li>
<li>
<p>The Java 17, Ruby 3.0, Python 3.10, and PHP 8.1 runtimes come into preview in App Engine and Cloud Functions</p>
</li>
</ul>
<p>Let&rsquo;s have a closer look. First of all, the access to App Engine legacy bundled services for second generation runtimes for <a href="https://cloud.google.com/appengine/docs/standard/java-gen2/services/access">Java</a>, <a href="https://cloud.google.com/appengine/docs/standard/python3/services/access">Python</a> and <a href="https://cloud.google.com/appengine/docs/standard/go/services/access">Go</a> is now Generally Available. In the past, for example for the Java platform, only Java 8 (a first generation runtime) could access the <a href="https://cloud.google.com/appengine/docs/standard/java-gen2/reference/services/bundled">built-in APIs</a> like Memcache, Images, Mail, or Task Queues. Now, if you use the Java 11 runtime (a second generation runtime), you can access those services as well as all the <a href="https://cloud.google.com/java/docs/reference">Google Cloud APIs</a>. For example, you can now store transient cached data in Memcache, or send an email to users of your application in a second generation runtime. Same thing for Python and Go developers, you can take advantage of the bundled services as well. If you&rsquo;re still using an old runtime version, this will further ease the transition to newer versions. Be sure to check it out and upgrade.</p>
<p>Next, let&rsquo;s continue with a fresh bean and a shiny gem, mixed in with some friendly animals, with the <strong>preview of the Java 17, Ruby 3.0, Python 3.10 and PHP 8.1 runtimes for both App Engine and Cloud Functions</strong>. What about having a look at what&rsquo;s new in those language versions?</p>
<h2 id="java">Java</h2>
<p>Between the two Long-Term-Support versions of Java 11 and 17, a lot of new features have landed. Java developers can now write text blocks for strings spanning several lines, without having to concatenate multiple strings manually. The switch construct has evolved to become an expression, which lets you break away from the <code>break</code> keyword, and paves the way for more advanced pattern matching capabilities. Speaking of which, the <code>instanceof</code> keyword is indeed offering some pattern matching evolution, to avoid obvious but useless casts. Records allow you to create more streamlined immutable data classes, rather than writing your own Java beans for that purpose with proper <code>hashCode()</code>, <code>equals()</code> or <code>toString()</code> methods. For more control over your class hierarchy, sealed class gives you more control over the extension of your classes.</p>
<h2 id="ruby">Ruby</h2>
<p>With Ruby 3.0, the big highlights were on performance, static type checking, and concurrency. The goal to make Ruby 3.0, three times faster on some benchmarks than Ruby 2.0 was reached, making your code run more swiftly. Also, Ruby programs can be annotated with some typing information, which allow type checkers to take advantage of those types to provide static type checking, to improve the quality of your code. For concurrency and parallelism, a new actor-inspired concurrency primitive called Ractor helps taming multiple cores in parallel, for your demanding workloads. And a fiber scheduler is introduced for intercepting blocking operations. And beyond those headlines, many improvements to various Ruby APIs have also landed.</p>
<h2 id="python">Python</h2>
<p>In Python 3.10, the parser gives better and clearer error messages for syntax errors (with more accurate error location), also for indentation, attribute, and name errors, which greatly help developers to find the problems in their code. Structural pattern matching lands with a new <code>match</code> and <code>case</code> statement construct. Further PEP improvements are tackling more robust type hints for static type checkers. Parenthesized context managers have been added to make the code prettier when spanning a long collection of context managers across several lines. </p>
<h2 id="php">PHP</h2>
<p>With version 8.1, PHP gets a pretty major update. First, let&rsquo;s start with a new <code>enum</code> syntax construct instead of creating constants in classes, and you get validation out of the box. Classes now have the ability to define final class constants. The new <code>readonly</code> properties can&rsquo;t be changed after initialization, which is great for value objects and DTOs. A first class callable syntax is introduced, allowing you to get a reference to any function, with a short syntax. Developers will also find further improvements to initializers, that make it possible to even have nested attributes, using objects as default parameter values, static variables, and global constants. One last nice addition we can mention is the introduction of fibers to implement lightweight cooperative concurrency.</p>
<h2 id="your-turn">Your turn</h2>
<p>Gems, beans, elephants, snakes: there&rsquo;s something great in those new language versions for every developer. Thus, with these new runtimes in Preview, Java, Ruby, Python and PHP developers can update or develop new App Engine apps and Cloud Functions using the latest and greatest versions of their favorite languages. Be sure to check out the documentation for App Engine (<a href="https://cloud.google.com/appengine/docs/standard/java-gen2/runtime">Java</a>, <a href="https://cloud.google.com/appengine/docs/standard/ruby/runtime">Ruby</a>, <a href="https://cloud.google.com/appengine/docs/standard/python3/runtime">Python</a>, <a href="https://cloud.google.com/appengine/docs/standard/php7/runtime">PHP</a>) and Cloud Functions (<a href="https://cloud.google.com/functions/docs/concepts/java-runtime">Java</a>, <a href="https://cloud.google.com/functions/docs/concepts/ruby-runtime">Ruby</a>, <a href="https://cloud.google.com/functions/docs/concepts/python-runtime">Python</a>, <a href="https://cloud.google.com/functions/docs/concepts/php-runtime">PHP</a>). We&rsquo;re looking forward to hearing from you about how you&rsquo;ll take advantage of those new language runtimes.</p>
<p><a href="https://cloud.google.com/blog/products/serverless/introducing-the-next-generation-of-cloud-functions"></a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Schedule a Workflow Execution</title><link>https://glaforge.dev/posts/2022/02/09/schedule-a-workflow-execution/</link><pubDate>Wed, 09 Feb 2022 17:06:51 +0100</pubDate><guid>https://glaforge.dev/posts/2022/02/09/schedule-a-workflow-execution/</guid><description>&lt;p>There are &lt;a href="https://cloud.google.com/workflows/docs/executing-workflow">different ways to launch the execution&lt;/a> of a workflow. In previous articles, we mentioned that you can &lt;a href="https://cloud.google.com/workflows/docs/quickstart-gcloud">use the gcloud command-line&lt;/a> tool to create an execution, you can also use the various &lt;a href="https://cloud.google.com/workflows/docs/quickstart-client-libraries">client libraries&lt;/a> to invoke Workflows, or use the &lt;a href="https://cloud.google.com/workflows/docs/reference/executions/rest">REST API&lt;/a>. A workflow itself can also invoke other workflows!&lt;/p>
&lt;p>But today, I&amp;rsquo;d like to tell you how to schedule the execution of a workflow. For that purpose, we&amp;rsquo;ll take advantage of &lt;a href="https://cloud.google.com/scheduler">Cloud Scheduler&lt;/a>. The &lt;a href="https://cloud.google.com/workflows/docs/schedule-workflow">documentation&lt;/a> is actually covering this topic in detail, so be sure to grab all the info there. However, I&amp;rsquo;ll go quickly through the steps, and tell you about a nice new feature in the cloud console to ease the scheduling of workflows!&lt;/p></description><content:encoded>
<![CDATA[<p>There are <a href="https://cloud.google.com/workflows/docs/executing-workflow">different ways to launch the execution</a> of a workflow. In previous articles, we mentioned that you can <a href="https://cloud.google.com/workflows/docs/quickstart-gcloud">use the gcloud command-line</a> tool to create an execution, you can also use the various <a href="https://cloud.google.com/workflows/docs/quickstart-client-libraries">client libraries</a> to invoke Workflows, or use the <a href="https://cloud.google.com/workflows/docs/reference/executions/rest">REST API</a>. A workflow itself can also invoke other workflows!</p>
<p>But today, I&rsquo;d like to tell you how to schedule the execution of a workflow. For that purpose, we&rsquo;ll take advantage of <a href="https://cloud.google.com/scheduler">Cloud Scheduler</a>. The <a href="https://cloud.google.com/workflows/docs/schedule-workflow">documentation</a> is actually covering this topic in detail, so be sure to grab all the info there. However, I&rsquo;ll go quickly through the steps, and tell you about a nice new feature in the cloud console to ease the scheduling of workflows!</p>
<p>First, you need to have both Workflows and Cloud Scheduler enabled:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud services <span style="color:#007020">enable</span> cloudscheduler.googleapis.com workflows.googleapis.com
</span></span></code></pre></div><p>Cloud Scheduler will need a service account with <code>workflows.invoker</code> role, to be allowed to call Workflows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud iam service-accounts create workflows_caller_sa
</span></span><span style="display:flex;"><span>gcloud projects add-iam-policy-binding MY_PROJECT_ID <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>  --member serviceAccount:workflows_caller_sa@MY_PROJECT_ID.iam.gserviceaccount.com <span style="color:#4070a0;font-weight:bold">\\</span>
</span></span><span style="display:flex;"><span>  --role roles/workflows.invoker
</span></span></code></pre></div><p>Now it&rsquo;s time to create the cron job:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud scheduler <span style="color:#007020">jobs</span> create http every_5_minute_schedule <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --schedule<span style="color:#666">=</span><span style="color:#4070a0">&#34;*/5 * * * *&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --uri<span style="color:#666">=</span><span style="color:#4070a0">&#34;https://workflowexecutions.googleapis.com/v1/projects/MY_PROJECT_ID/locations/REGION_NAME/workflows/WORKFLOW_NAME/executions&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --message-body<span style="color:#666">=</span><span style="color:#4070a0">&#34;{\&#34;argument\&#34;: \&#34;DOUBLE_ESCAPED_JSON_STRING\&#34;}&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --time-zone<span style="color:#666">=</span><span style="color:#4070a0">&#34;America/New_York&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --oauth-service-account-email<span style="color:#666">=</span><span style="color:#4070a0">&#34;workflows_caller_sa@MY_PROJECT_ID.iam.gserviceaccount.com&#34;</span>
</span></span></code></pre></div><p>Here, you can see that Scheduler will run every 5 minutes (using the cron notation), and that it&rsquo;s going to call the Workflows REST API to create a new execution. You can also pass an argument for the workflow input.</p>
<p>The cool new feature I was eager to mention today was the direct integration of the scheduling as part of the Workflows creation flow, in the cloud console.</p>
<p>Now, when you create a new workflow, you can select a trigger:</p>
<p><figure>
  <a href="#img-e5f619bee96e60094321deaaef391a22">
    <img src="/img/schedule-workflow/scheduler-trigger-1-600.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e5f619bee96e60094321deaaef391a22">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/schedule-workflow/scheduler-trigger-1-600.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Click on the <code>ADD NEW TRIGGER</code> button, and select <code>Scheduler</code>. A side panel on the right will show up, and you will be able to specify the schedule to create, directly integrated, instead of having to head over to the Cloud Scheduler product section:</p>
<p><figure>
  <a href="#img-68eebade558b59676036f9a4ca947ad0">
    <img src="/img/schedule-workflow/scheduler-trigger-2-600.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-68eebade558b59676036f9a4ca947ad0">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/schedule-workflow/scheduler-trigger-2-600.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And there, you can specify the various details of the schedule! It&rsquo;s nice to see both products nicely integrated, to ease the flow of creating a scheduled workflow.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Using the Secret Manager connector for Workflows to call an authenticated service</title><link>https://glaforge.dev/posts/2022/02/04/using-the-secret-manager-connector-for-workflows-to-call-an-authenticated-service/</link><pubDate>Fri, 04 Feb 2022 22:26:59 +0100</pubDate><guid>https://glaforge.dev/posts/2022/02/04/using-the-secret-manager-connector-for-workflows-to-call-an-authenticated-service/</guid><description>&lt;p>Workflows allows you to call APIs, whether from or hosted on Google Cloud, or any external API in the wild.
A few days ago, for example, we saw an example on
&lt;a href="https://glaforge.dev/posts/2022/02/01/sending-an-email-with-sendgrid-from-workflows/">how to use the SendGrid API to send emails from a workflow&lt;/a>.
However, in that article, I had the API key hard-coded into my workflow, which is a bad practice.
Instead, we can store secrets in &lt;a href="https://cloud.google.com/secret-manager">Secret Manager&lt;/a>.
Workflows has a specific &lt;a href="https://cloud.google.com/workflows/docs/reference/googleapis/secretmanager/Overview">connector for Secret Manager&lt;/a>,
and a useful method to access secrets.&lt;/p></description><content:encoded>
<![CDATA[<p>Workflows allows you to call APIs, whether from or hosted on Google Cloud, or any external API in the wild.
A few days ago, for example, we saw an example on
<a href="https://glaforge.dev/posts/2022/02/01/sending-an-email-with-sendgrid-from-workflows/">how to use the SendGrid API to send emails from a workflow</a>.
However, in that article, I had the API key hard-coded into my workflow, which is a bad practice.
Instead, we can store secrets in <a href="https://cloud.google.com/secret-manager">Secret Manager</a>.
Workflows has a specific <a href="https://cloud.google.com/workflows/docs/reference/googleapis/secretmanager/Overview">connector for Secret Manager</a>,
and a useful method to access secrets.</p>
<p>In this article, we&rsquo;ll learn two things:</p>
<ul>
<li>How to access secrets stored in Secret Manager with the Workflows connector</li>
<li>How to call an API that requires basic authentication</li>
</ul>
<p>Let&rsquo;s access the secrets I need to do my basic auth call to the API I need to call:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">get_secret_user</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>googleapis.secretmanager.v1.projects.secrets.versions.accessString<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">secret_id</span>:<span style="color:#bbb">  </span>basicAuthUser<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>secret_user<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">get_secret_password</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>googleapis.secretmanager.v1.projects.secrets.versions.accessString<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">secret_id</span>:<span style="color:#bbb">  </span>basicAuthPassword<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>secret_password<span style="color:#bbb">
</span></span></span></code></pre></div><p>The user login and password are now stored in variables that I can reuse in my workflow.
I will create the Base64 encoded user:password string required to pass in the authorization header:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">assign_user_password</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">encodedUserPassword</span>:<span style="color:#bbb">  </span>${base64.encode(text.encode(secret_user  +  &#34;:&#34;  +  secret_password))}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Equipped with my encoded user:password string, I can now call my API (here a cloud function)
by added an authorization header with basic authentication (and return the output of the function):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">call_function</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://europe-west1-workflows-days.cloudfunctions.net/basicAuthFn<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">headers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">Authorization</span>:<span style="color:#bbb">  </span>${&#34;Basic  &#34; + encodedUserPassword}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>fn_output<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">return_result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${fn_output.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Workflows has built-in
<a href="https://cloud.google.com/workflows/docs/authentication#making_authenticated_requests">OAuth2 and OIDC support for authenticating to Google hosted APIs, functions and Cloud Run services</a>,
but it&rsquo;s also useful to know how to invoke other authenticated services, like those requiring basic auth, or other bearer tokens.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Upload and use JSON data in your workflow from GCS</title><link>https://glaforge.dev/posts/2022/02/03/upload-and-use-json-data-in-your-workflow-from-gcs/</link><pubDate>Thu, 03 Feb 2022 22:39:13 +0100</pubDate><guid>https://glaforge.dev/posts/2022/02/03/upload-and-use-json-data-in-your-workflow-from-gcs/</guid><description>&lt;p>Following up the &lt;a href="https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/">article&lt;/a>
on writing and reading JSON files in cloud storage buckets (GCS), we saw that we could access the data of the JSON file, and use it in our workflow.
Let&amp;rsquo;s have a look at a concrete use of this.&lt;/p>
&lt;p>Today, we&amp;rsquo;ll take advantage of this mechanism to avoid hard-coding the URLs of the APIs we call from our workflow.
That way, it makes the workflow more portable across environments.&lt;/p></description><content:encoded>
<![CDATA[<p>Following up the <a href="https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/">article</a>
on writing and reading JSON files in cloud storage buckets (GCS), we saw that we could access the data of the JSON file, and use it in our workflow.
Let&rsquo;s have a look at a concrete use of this.</p>
<p>Today, we&rsquo;ll take advantage of this mechanism to avoid hard-coding the URLs of the APIs we call from our workflow.
That way, it makes the workflow more portable across environments.</p>
<p>Let&rsquo;s regroup the logic for reading and loading the JSON data in a reusable subworkflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">read_env_from_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[bucket,  object]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">read_from_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>${&#34;https://storage.googleapis.com/download/storage/v1/b/&#34;  +  bucket  +  &#34;/o/&#34;  +  object}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">alt</span>:<span style="color:#bbb">  </span>media<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>env_file_json_content<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${env_file_json_content.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You call this subworkflow with two parameters: the bucket name, and the object or file name that you want to load.</p>
<p>Now let&rsquo;s use it from the main workflow. We need a first step to call the subworkflow to load a specific file from a specific bucket.
The subworkflow below will return the content of the JSON data in the env_details variable.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">​​main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">load_env_details</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>read_env_from_gcs<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">bucket</span>:<span style="color:#bbb">  </span>workflow_environment_info<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">object</span>:<span style="color:#bbb">  </span>env-info.json<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>env_details<span style="color:#bbb">
</span></span></span></code></pre></div><p>Imagine the JSON file contains a JSON object with a <code>SERVICE_URL</code> key, pointing at the URL of a service,
then you can call the service with the following expression: <code>${env_details.SERVICE_URL}</code> as shown below.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">call_service</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>${env_details.SERVICE_URL}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>service_result<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${service_result.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is great for avoiding hardcoding certain values in your workflow definitions.
However, for true environment-specific deployments, this is not yet ideal, as you would have to point to a different file in the bucket, or use a different bucket.
And that information is currently hardcoded in the definition when you make the call to the subworkflow.
But if you follow some naming conventions for the project names and bucket names, that map to environments, this can work!
(ie. <code>PROD_bucket</code> vs <code>DEV_bucket</code>, or <code>PROD-env-info.json</code> vs <code>DEV-env-info.json</code>)</p>
<p>Let&rsquo;s wait for the support of environment variables in Workflows!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Sending an email with SendGrid from Workflows</title><link>https://glaforge.dev/posts/2022/02/01/sending-an-email-with-sendgrid-from-workflows/</link><pubDate>Tue, 01 Feb 2022 21:04:31 +0100</pubDate><guid>https://glaforge.dev/posts/2022/02/01/sending-an-email-with-sendgrid-from-workflows/</guid><description>&lt;p>Following up the &lt;a href="https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/">article&lt;/a>
on writing and reading JSON files in cloud storage buckets, we saw that we could access the data of the JSON file,
and use it in our workflow. Let&amp;rsquo;s have a look at a concrete use of this.&lt;/p>
&lt;p>Today, we&amp;rsquo;ll take advantage of this mechanism to avoid hard-coding the URLs of the APIs we call from our workflow.
That way, it makes the workflow more portable across environments.&lt;/p></description><content:encoded>
<![CDATA[<p>Following up the <a href="https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/">article</a>
on writing and reading JSON files in cloud storage buckets, we saw that we could access the data of the JSON file,
and use it in our workflow. Let&rsquo;s have a look at a concrete use of this.</p>
<p>Today, we&rsquo;ll take advantage of this mechanism to avoid hard-coding the URLs of the APIs we call from our workflow.
That way, it makes the workflow more portable across environments.</p>
<p>Let&rsquo;s regroup the logic for reading and loading the JSON data in a reusable subworkflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">read_env_from_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[bucket,  object]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">read_from_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>${&#34;https://storage.googleapis.com/download/storage/v1/b/&#34;  +  bucket  +  &#34;/o/&#34;  +  object}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">alt</span>:<span style="color:#bbb">  </span>media<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>env_file_json_content<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${env_file_json_content.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You call this subworkflow with two parameters: the bucket name, and the object or file name that you want to load.</p>
<p>Now let&rsquo;s use it from the main workflow. We need a first step to call the subworkflow to load a specific file from a specific bucket.
The subworkflow below will return the content of the JSON data in the <code>env_details</code> variable.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">​​main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">load_env_details</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>read_env_from_gcs<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">bucket</span>:<span style="color:#bbb">  </span>workflow_environment_info<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">object</span>:<span style="color:#bbb">  </span>env-info.json<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>env_details<span style="color:#bbb">
</span></span></span></code></pre></div><p>Imagine the JSON file contains a JSON object with a <code>SERVICE_URL</code> key, pointing at the URL of a service,
then you can call the service with the following expression: <code>${env_details.SERVICE_URL}</code> as shown below.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">call_service</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>${env_details.SERVICE_URL}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>service_result<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${service_result.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is great for avoiding hardcoding certain values in your workflow definitions.
However, for true environment-specific deployments, this is not yet ideal,
as you would have to point to a different file in the bucket, or use a different bucket.
And that information is currently hardcoded in the definition when you make the call to the subworkflow.
But if you follow some naming conventions for the project names and bucket names, that map to environments,
this can work! (ie. <code>PROD_bucket</code> vs <code>DEV_bucket</code>, or <code>PROD-env-info.json</code> vs <code>DEV-env-info.json</code>)</p>
<p>Let&rsquo;s wait for the support of environment variables in Workflows!
For notification purposes, especially in an asynchronous way, email is a great solution.
I wanted to add an email notification step in <a href="https://cloud.google.com/workflows">Google Cloud Workflows</a>.
Since GCP doesn&rsquo;t have an email service, I looked at the various email services available in the cloud: SendGrid, Mailgun, Mailjet,
and even ran a quick Twitter <a href="https://twitter.com/glaforge/status/1488444661211533312">poll</a> to see what folks in the wild are using.
I experimented with SendGrid, and the <a href="https://signup.sendgrid.com/">sign up</a> process was pretty straightforward,
as I was able to <a href="https://docs.sendgrid.com/for-developers/sending-email/api-getting-started">get started</a> quickly,
by creating an API key, and sending my first email with cURL command.</p>
<p>Now comes the part where I needed to call that API from my workflow definition.
And that&rsquo;s actually pretty straightforward as well.
Let&rsquo;s see that in action:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">retrieve_api_key</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">SENDGRID_API_KEY</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;MY_API_KEY&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">send_email</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://api.sendgrid.com/v3/mail/send<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">headers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">Content-Type</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">Authorization</span>:<span style="color:#bbb">  </span>${&#34;Bearer  &#34; + SENDGRID_API_KEY}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">personalizations</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>- <span style="color:#062873;font-weight:bold">to</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>- <span style="color:#062873;font-weight:bold">email</span>:<span style="color:#bbb"> </span>to@example.com<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">from</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">email</span>:<span style="color:#bbb"> </span>from@example.com<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">subject</span>:<span style="color:#bbb"> </span>Sending an email from Workflows<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>- <span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>text/plain<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">value</span>:<span style="color:#bbb"> </span>Here&#39;s the body of my email<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>email_result<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">return_result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${email_result.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the <code>retrieve_api_key</code> step, I simply hard-coded the SendGrid API key.
However, you can of course store that secret in Secret Manager,
and then fetch the secret key thanks to the Workflows Secret Manager connector (that&rsquo;s probably worth a dedicated article!)</p>
<p>Then, in the <code>send_email</code> step, I prepare my HTTP POST request to the SendGrid API endpoint.
I specify the content type, and of course, the authorization using the SendGrid API key.
Next, I prepare the body of that request, describing my email,
with a <code>from</code> field with a registered email user that I defined in SendGrid, a <code>to</code> field corresponding to the recipient,
an email <code>subject</code> and <code>body</code> (just plain text, here).
And that&rsquo;s pretty much it!
I just translated the JSON body sent in the <a href="https://app.sendgrid.com/guide/integrate/langs/curl">cURL example</a> from SendGrid&rsquo;s documentation,
into YAML (using a handy JSON to YAML conversion <a href="https://www.json2yaml.com/">utility</a>)</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Smarter Applications With Document Ai Workflows and Cloud Functions</title><link>https://glaforge.dev/posts/2022/02/01/smarter-applications-with-document-ai-workflows-and-cloud-functions/</link><pubDate>Tue, 01 Feb 2022 13:35:51 +0100</pubDate><guid>https://glaforge.dev/posts/2022/02/01/smarter-applications-with-document-ai-workflows-and-cloud-functions/</guid><description>&lt;p>At enterprises across industries, documents are at the center of core business processes. Documents store a treasure trove of valuable information whether it&amp;rsquo;s a company&amp;rsquo;s invoices, HR documents, tax forms and much more. However, the unstructured nature of documents make them difficult to work with as a data source. We call this &lt;a href="https://www.gartner.com/en/information-technology/glossary/dark-data">&amp;ldquo;dark data&amp;rdquo;&lt;/a> or unstructured data that businesses collect, process and store but do not utilize for purposes such as analytics, monetization, etc. These documents in pdf or image formats, often trigger complex processes that have historically relied on fragmented technology and manual steps. With compute solutions on Google Cloud and &lt;a href="https://cloud.google.com/document-ai">Document AI&lt;/a>, you can create seamless integrations and easy to use applications for your users. Document AI is a platform and a family of solutions that help businesses to transform documents into structured data backed by machine learning. In this blog post we&amp;rsquo;ll walk you through how to use Serverless technology to process documents with &lt;a href="https://cloud.google.com/functions">Cloud Functions&lt;/a>, and with workflows of business processes orchestrating microservices, API calls, and functions, thanks to &lt;a href="https://cloud.google.com/workflows">Workflows&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>At enterprises across industries, documents are at the center of core business processes. Documents store a treasure trove of valuable information whether it&rsquo;s a company&rsquo;s invoices, HR documents, tax forms and much more. However, the unstructured nature of documents make them difficult to work with as a data source. We call this <a href="https://www.gartner.com/en/information-technology/glossary/dark-data">&ldquo;dark data&rdquo;</a> or unstructured data that businesses collect, process and store but do not utilize for purposes such as analytics, monetization, etc. These documents in pdf or image formats, often trigger complex processes that have historically relied on fragmented technology and manual steps. With compute solutions on Google Cloud and <a href="https://cloud.google.com/document-ai">Document AI</a>, you can create seamless integrations and easy to use applications for your users. Document AI is a platform and a family of solutions that help businesses to transform documents into structured data backed by machine learning. In this blog post we&rsquo;ll walk you through how to use Serverless technology to process documents with <a href="https://cloud.google.com/functions">Cloud Functions</a>, and with workflows of business processes orchestrating microservices, API calls, and functions, thanks to <a href="https://cloud.google.com/workflows">Workflows</a>.</p>
<p>At Cloud Next 2021, we presented how to build easy <a href="https://cloud.withgoogle.com/next/catalog?session=DEV202#application-development">AI-powered applications with Google Cloud</a>. We introduced a sample application for handling incoming expense reports, analyzing expense receipts with <a href="https://cloud.google.com/solutions/procurement-doc-ai">Procurement Document A</a>I, a DocAI solution for automating procurement data capture from forms including invoices, utility statements and more. Then organizing the logic of a report approval process with Workflows, and used Cloud Functions as glue to invoke the workflow, and do analysis of the parsed document.</p>
<p><figure>
  <a href="#img-392efee994103a4297281beee0a4de87">
    <img src="/img/smart-expense/smart-expenses-screens.max-1700x1700.png"
      alt="/img/smart-expense/smart-expenses-screens.max-1700x1700.png"
       />
  </a>
  <figcaption>/img/smart-expense/smart-expenses-screens.max-1700x1700.png</figcaption>
</figure>
<div class="lightbox" id="img-392efee994103a4297281beee0a4de87">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/smart-expense/smart-expenses-screens.max-1700x1700.png"
    alt="/img/smart-expense/smart-expenses-screens.max-1700x1700.png"
     />
  <div class="lightbox-caption">/img/smart-expense/smart-expenses-screens.max-1700x1700.png</div>
</div>
</p>
<p>We also open sourced the code on this <a href="https://github.com/GoogleCloudPlatform/smart-expenses">Github repository</a>, if you&rsquo;re interested in learning more about this application.</p>
<p><figure>
  <a href="#img-82b426d725666ed075313751834cc68b">
    <img src="/img/smart-expense/architecture-diagram.max-1500x1500.png"
      alt="/img/smart-expense/architecture-diagram.max-1500x1500.png"
       />
  </a>
  <figcaption>/img/smart-expense/architecture-diagram.max-1500x1500.png</figcaption>
</figure>
<div class="lightbox" id="img-82b426d725666ed075313751834cc68b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/smart-expense/architecture-diagram.max-1500x1500.png"
    alt="/img/smart-expense/architecture-diagram.max-1500x1500.png"
     />
  <div class="lightbox-caption">/img/smart-expense/architecture-diagram.max-1500x1500.png</div>
</div>
</p>
<p>In the above diagram, there are two user journeys: the employee submitting an expense report where multiple receipts are processed at once, and the manager validating or rejecting the expense report. </p>
<p>First, the employee goes to the website, powered by <a href="https://vuejs.org/">Vue.js</a> for the frontend progressive JavaScript framework and <a href="https://shoelace.style/">Shoelace</a> for the library of web components. The website is hosted via <a href="https://firebase.google.com/docs/hosting">Firebase Hosting</a>. The frontend invokes an <a href="https://cloud.google.com/functions/docs/writing/http">HTTP function</a> that triggers the execution of our business <a href="https://github.com/GoogleCloudPlatform/smart-expenses/blob/main/workflow.yaml">workflow</a>, defined using the Workflows YAML syntax. </p>
<p>Workflows is able to handle long-running operations without any additional code required, in our case we are asynchronously processing a set receipt files. Here, the Document AI <a href="https://cloud.google.com/blog/topics/developers-practitioners/better-service-orchestration-workflows">connector</a> directly calls the batch processing endpoint for service. This API returns a long-running operation: if you poll the API, the operation state will be &ldquo;RUNNING&rdquo; until it has reached a &ldquo;SUCCEEDED&rdquo; or &ldquo;FAILED&rdquo; state. You would have to wait for its completion. However, Workflows&rsquo; <a href="https://cloud.google.com/workflows/docs/connectors">connectors</a> handle such long-running operations, without you having to poll the API multiple times till the state changes. Here&rsquo;s how we call the batch processing operation of the <a href="https://cloud.google.com/workflows/docs/reference/googleapis/documentai/Overview">Document AI connector</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">invoke_document_ai</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.documentai.v1.projects.locations.processors.batchProcess<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>${&#34;projects/&#34; + project + &#34;/locations/eu/processors/&#34; + processorId}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">location</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;eu&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">inputDocuments</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">gcsPrefix</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">gcsUriPrefix</span>:<span style="color:#bbb"> </span>${bucket_input + report_id}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">documentOutputConfig</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">gcsOutputConfig</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">gcsUri</span>:<span style="color:#bbb"> </span>${bucket_output + report_id}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">skipHumanReview</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>document_ai_response<span style="color:#bbb">
</span></span></span></code></pre></div><p>Machine learning uses state of the art Vision and Natural Language Processing models to intelligently extract schematized data from documents with Document AI. As a developer, you don&rsquo;t have to figure out how to fine tune or reframe the receipt pictures, or how to find the relevant field and information in the receipt. It&rsquo;s Document AI&rsquo;s job to help you here: it will return a JSON document whose fields are: <code>line_item</code>, <code>currency</code>, <code>supplier_name</code>, <code>total_amount</code>, etc. Document AI is capable of understanding standardized papers and forms, including invoices, lending documents, pay slips, driver licenses, and more.</p>
<p>A cloud function retrieves all the relevant fields of the receipts, and makes its own tallies, before submitting the expense report for approval to the manager. Another useful feature of Workflows is put to good use: Callbacks, that we <a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-workflows-callbacks">introduced</a> last year. In the workflow definition we create a callback endpoint, and the workflow execution will wait for the callback to be called to continue its flow, thanks to those two instructions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">create_callback</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>events.create_callback_endpoint<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">http_callback_method</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;POST&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>callback_details<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#0e84b5;font-weight:bold">...</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">await_callback</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">try</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>events.await_callback<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">callback</span>:<span style="color:#bbb"> </span>${callback_details}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span><span style="color:#40a070">3600</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>callback_request<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">except</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">as</span>:<span style="color:#bbb"> </span>e<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">update_status_to_error</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#0e84b5;font-weight:bold">...</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>In this example application, we combined the intelligent capabilities of Document AI to transform complex image documents into usable structured data, with Cloud Functions for data transformation, process triggering, and callback handling logic, and Workflows enabled us to orchestrate the underlying business process and its service call logic.</p>
<h2 id="going-further">Going further </h2>
<p>If you&rsquo;re looking to make sense of your documents, turning dark data into structured information, be sure to check out what <a href="https://cloud.google.com/document-ai">Document AI</a> offers. You can also get your hands on a <a href="https://codelabs.developers.google.com/codelabs/docai-form-parser-v3-node#0">codelab</a> to get started quickly, in which you&rsquo;ll get a chance at processing handwritten forms. If you want to explore <a href="https://cloud.google.com/workflows">Workflows</a>, <a href="https://cloud.google.com/workflows/docs/quickstarts">quickstarts</a> are available to guide you through your first steps, and likewise, another <a href="https://codelabs.developers.google.com/codelabs/cloud-workflows-intro?hl=en#0">codelab</a> explores the basics of Workflows. As mentioned earlier, for a concrete example, the source code of our <a href="https://github.com/GoogleCloudPlatform/smart-expenses">smart expense application</a> is available on Github. Don&rsquo;t hesitate to reach out to us at <a href="https://twitter.com/glaforge">@glaforge</a> and <a href="https://twitter.com/asrivas_dev">@asrivas_dev</a> to discuss smart scalable apps with us.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Open sourcing the App Engine Standard Java Runtime</title><link>https://glaforge.dev/posts/2022/01/26/open-sourcing-the-app-engine-standard-java-runtime/</link><pubDate>Wed, 26 Jan 2022 19:07:57 +0100</pubDate><guid>https://glaforge.dev/posts/2022/01/26/open-sourcing-the-app-engine-standard-java-runtime/</guid><description>&lt;p>One year after Google App Engine was &lt;a href="http://googleappengine.blogspot.com/2008/04/introducing-google-app-engine-our-new.html">released&lt;/a> in 2008, Java became the &lt;a href="http://googleappengine.blogspot.com/2009/04/seriously-this-time-new-language-on-app.html">second language runtime&lt;/a> available on the platform. Java developers were able to deploy and scale their servlet-based web applications easily, without worrying about infrastructure management. Not only Java was able to run then, but alternative JVM languages, like &lt;a href="https://spring.io/blog/2009/04/08/write-your-google-app-engine-applications-in-groovy">Apache Groovy&lt;/a>, and &lt;a href="https://kotlinlang.org/">Kotlin&lt;/a> are also part of the game. Fast forward to today, we&amp;rsquo;re pleased to announce that the Java Runtime for App Engine is now available as open source, in the &lt;a href="https://github.com/GoogleCloudPlatform/appengine-java-standard">GoogleCloudPlatform/appengine-java-standard&lt;/a> repository on Github.&lt;/p></description><content:encoded>
<![CDATA[<p>One year after Google App Engine was <a href="http://googleappengine.blogspot.com/2008/04/introducing-google-app-engine-our-new.html">released</a> in 2008, Java became the <a href="http://googleappengine.blogspot.com/2009/04/seriously-this-time-new-language-on-app.html">second language runtime</a> available on the platform. Java developers were able to deploy and scale their servlet-based web applications easily, without worrying about infrastructure management. Not only Java was able to run then, but alternative JVM languages, like <a href="https://spring.io/blog/2009/04/08/write-your-google-app-engine-applications-in-groovy">Apache Groovy</a>, and <a href="https://kotlinlang.org/">Kotlin</a> are also part of the game. Fast forward to today, we&rsquo;re pleased to announce that the Java Runtime for App Engine is now available as open source, in the <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard">GoogleCloudPlatform/appengine-java-standard</a> repository on Github.</p>
<h2 id="whats-available">What&rsquo;s available?</h2>
<p>The <code>appengine-java-standard</code> repository contains the Java source code for the Java standard environment, the production runtime, the APIs, and the local SDK. </p>
<p>Zooming on the various directories of the project, you&rsquo;ll find the <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/tree/main/api/src/main/java/com/google/appengine/api">APIs</a> from the <code>com.google.appengine.api</code> package for using and accessing App Engine services like Datastore to store your data, Blobstore to save your binary blobs, Taskqueue to enqueue computing tasks, Memcache to cache expensive results you don&rsquo;t want to recompute, or Urlfetch to call external services. </p>
<p>The great developer experience of Google App Engine comes in part from the fact you can run your application locally on your development machine, thanks to the local development environment. The aforementioned services all have a local <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/tree/main/api_dev/src/main/java/com/google/appengine/api">implementation</a> enabling you to start your app on your machine, rather than having to deploy in the cloud each time you make a change.</p>
<p>To use the services in the cloud, however, you can use Google&rsquo;s built-in <a href="https://cloud.google.com/appengine/docs/standard/java/tools/remoteapi">remote APIs</a> (<a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/tree/main/remoteapi/src/main/java/com/google/appengine/tools/remoteapi">code</a>) that let you transparently access App Engine services from any Java application. For example, you can use the Remote API to access a production Datastore from an app running on your local machine. You can also use Remote API to access the Datastore of one App Engine application from a different App Engine application.</p>
<p>In the <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/tree/main/runtime/impl/src/main/java/com/google/apphosting">runtime folder</a>, you&rsquo;ll be able to see how your App Engine app is started, with the underlying <a href="https://www.eclipse.org/jetty/">Jetty</a> servlet container, and understand how the various components or services are configured on startup.</p>
<p>What&rsquo;s not open source today are the specific layers that tie App Engine to the underlying <a href="https://research.google.com/pubs/pub43438.html?hl=es">Borg</a> cluster management system, internal to the Google infrastructure, as developers can&rsquo;t replicate the whole Google backend easily in their own environment.</p>
<h2 id="why-make-it-open-source">Why make it open source?</h2>
<p>It&rsquo;s important for our customers to not be locked in a particular tool, environment, or vendor, and give them the liberty to run their workloads elsewhere, and understand how we actually run their code. That&rsquo;s why Google Cloud follows an <a href="https://cloud.google.com/open-cloud">Open Cloud</a> approach, and participates actively in the open source ecosystem. Our open cloud approach enables you to develop software faster, innovate more easily, and scale more efficiently&mdash;while also reducing technology risk. Through the years, we&rsquo;ve open sourced various technologies, like <a href="https://kubernetes.io/">Kubernetes</a>, based on our experience of running containers at scale, or the <a href="https://gvisor.dev/">gVisor</a> sandbox, an application kernel for containers that provides efficient defense-in-depth anywhere, and which was pioneered by the App Engine environment, as gVisor was its sandbox environment.</p>
<p>By open sourcing the App Engine Java runtime, we&rsquo;re making the first step towards letting you run the whole App Engine environment wherever you want: on your local development environment, on-premise in your own data center, or potentially on our other computing platforms like <a href="https://cloud.google.com/run/">Cloud Run</a>. We&rsquo;re also making a key step towards easier transitions for future runtimes based on newer Long-Term Support versions of the Java programming language. </p>
<p>Finally, being able to compile, test and release the Java runtime environment for Java 8, Java 11 and soon Java 17 from an open source platform is much easier than relying on the huge internal Google mono repository system which is intended to support only one single version of everything, including a single JDK toolchain. We are releasing the binary artifacts in the <a href="https://repo1.maven.org/maven2/com/google/appengine/">Maven central repository</a>, including Javadocs and sources to find the relevant code regarding exceptions raised during runtime execution.</p>
<h2 id="going-further">Going further</h2>
<p>If you&rsquo;re not familiar with App Engine yet, be sure to check out our <a href="https://cloud.google.com/appengine">online resources</a> about this powerful and scalable serverless application platform on our website. And get started with the tutorials on the <a href="https://cloud.google.com/appengine/docs/standard/java/runtime">Java 8</a> or <a href="https://cloud.google.com/appengine/docs/standard/java11/services/access">Java 11 standard environment</a>. To learn more about this open source App Engine Java runtime, please visit our <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard">repository</a> on Github, to discover how the serverless secret sauce is made. And if you&rsquo;re feeling like helping and <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/blob/main/CONTRIBUTING.md">contributing</a>, we&rsquo;re looking forward to hearing from you, by filing new <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/issues">tickets</a> or by preparing some <a href="https://github.com/GoogleCloudPlatform/appengine-java-standard/pulls">pull requests</a>.</p>
<p><a href="https://cloud.google.com/blog/products/application-development/turn-it-up-to-eleven-java-11-runtime-comes-to-app-engine"></a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Reading in and writing a JSON file to a storage bucket from a workflow</title><link>https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/</link><pubDate>Fri, 21 Jan 2022 21:28:16 +0100</pubDate><guid>https://glaforge.dev/posts/2022/01/21/reading-in-and-writing-a-json-file-to-a-storage-bucket-from-a-workflow/</guid><description>&lt;p>&lt;a href="https://cloud.google.com/workflows">Workflows&lt;/a> provides several &lt;a href="https://cloud.google.com/workflows/docs/reference/googleapis">connectors&lt;/a>
for interacting with various Google Cloud APIs and services.
In the past, I&amp;rsquo;ve used for example the &lt;a href="https://cloud.google.com/workflows/docs/reference/googleapis/documentai/Overview">Document AI connector&lt;/a>
to parse documents like expense receipts,
or the &lt;a href="https://cloud.google.com/workflows/docs/reference/googleapis/secretmanager/Overview">Secret Manager connector&lt;/a>
to store and access secrets like passwords.
Another useful connector I was interested in using today was the
&lt;a href="https://cloud.google.com/workflows/docs/reference/googleapis/storage/Overview">Google Cloud Storage connector&lt;/a>,
to store and read files stored in storage buckets.&lt;/p>
&lt;p>Those connectors are auto-generated from their API discovery descriptors,
but there are some limitations currently that prevent, for example, to download the content of a file.
So instead of using the connector, I looked at the JSON API for cloud storage to see what it offered
(&lt;a href="https://cloud.google.com/workflows/docs/reference/googleapis/storage/v1/objects/insert">insert&lt;/a> and
&lt;a href="https://cloud.google.com/storage/docs/json_api/v1/objects/get">get&lt;/a> methods).&lt;/p></description><content:encoded>
<![CDATA[<p><a href="https://cloud.google.com/workflows">Workflows</a> provides several <a href="https://cloud.google.com/workflows/docs/reference/googleapis">connectors</a>
for interacting with various Google Cloud APIs and services.
In the past, I&rsquo;ve used for example the <a href="https://cloud.google.com/workflows/docs/reference/googleapis/documentai/Overview">Document AI connector</a>
to parse documents like expense receipts,
or the <a href="https://cloud.google.com/workflows/docs/reference/googleapis/secretmanager/Overview">Secret Manager connector</a>
to store and access secrets like passwords.
Another useful connector I was interested in using today was the
<a href="https://cloud.google.com/workflows/docs/reference/googleapis/storage/Overview">Google Cloud Storage connector</a>,
to store and read files stored in storage buckets.</p>
<p>Those connectors are auto-generated from their API discovery descriptors,
but there are some limitations currently that prevent, for example, to download the content of a file.
So instead of using the connector, I looked at the JSON API for cloud storage to see what it offered
(<a href="https://cloud.google.com/workflows/docs/reference/googleapis/storage/v1/objects/insert">insert</a> and
<a href="https://cloud.google.com/storage/docs/json_api/v1/objects/get">get</a> methods).</p>
<p>What I wanted to do was to store a JSON document, and to read a JSON document.
I haven&rsquo;t tried with other media types yet, like pictures or other binary files.
Anyhow, here&rsquo;s how to write a JSON file into a cloud storage bucket:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">assignment</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">bucket</span>:<span style="color:#bbb">  </span>YOUR_BUCKET_NAME_HERE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">write_to_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>${&#34;https://storage.googleapis.com/upload/storage/v1/b/&#34;  +  bucket  +  &#34;/o&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb">  </span>THE_FILE_NAME_HERE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb">  </span>Guillaume<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">age</span>:<span style="color:#bbb">  </span><span style="color:#40a070">99</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>In the file, I&rsquo;m storing a JSON document that contains a couple keys, defined in the body of that call.
By default, here, a JSON media type is assumed, so the body defined at the bottom in YAML is actually written as JSON in the resulting file.
Oh and of course, don&rsquo;t forget to change the names of the bucket and the object in the example above.</p>
<p>And now, here&rsquo;s how you can read the content of the file from the bucket:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[input]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">assignment</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">bucket</span>:<span style="color:#bbb">  </span>YOUR_BUCKET_NAME_HERE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb">  </span>THE_FILE_NAME_HERE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">read_from_gcs</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>${&#34;https://storage.googleapis.com/download/storage/v1/b/&#34;  +  bucket  +  &#34;/o/&#34;  +  name}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">alt</span>:<span style="color:#bbb">  </span>media<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>data_json_content<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">return_content</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${data_json_content.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This time we change the GCS URL from <code>upload</code> to <code>download</code>,
and we use the <code>alt=media</code> query parameter to instruct the GCS JSON API that we want to retrieve the content of the file (not just its metadata).
In the end, we return the body of that call, which contains the content.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>How to get the project ID in a Java Cloud Function</title><link>https://glaforge.dev/posts/2022/01/17/how-to-get-the-project-id-in-a-java-cloud-function/</link><pubDate>Mon, 17 Jan 2022 22:20:29 +0100</pubDate><guid>https://glaforge.dev/posts/2022/01/17/how-to-get-the-project-id-in-a-java-cloud-function/</guid><description>&lt;p>As I was working with my colleague &lt;a href="https://cloud.google.com/developers/advocates/sara-ford">Sara Ford&lt;/a>
on testing the Cloud Functions runtimes for the upcoming &amp;ldquo;second generation&amp;rdquo; of the product,
rebased on the &lt;a href="https://cloud.run/">Cloud Run&lt;/a> platform, I wrote a few simple functions for the Java runtime.
In one of those Java functions, I wanted to use Google Cloud Storage, to download a file from a bucket.
I took a look at the existing
&lt;a href="https://github.com/googleapis/google-cloud-java/blob/main/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/objects/DownloadObject.java">sample&lt;/a>
to download an object:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>Storage&lt;span style="color:#bbb"> &lt;/span>storage&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>StorageOptions.&lt;span style="color:#4070a0">newBuilder&lt;/span>()&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>.&lt;span style="color:#4070a0">setProjectId&lt;/span>(projectId)&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>.&lt;span style="color:#4070a0">build&lt;/span>()&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>.&lt;span style="color:#4070a0">getService&lt;/span>();&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>Blob&lt;span style="color:#bbb"> &lt;/span>blob&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#666">=&lt;/span>&lt;span style="color:#bbb"> &lt;/span>storage.&lt;span style="color:#4070a0">get&lt;/span>(BlobId.&lt;span style="color:#4070a0">of&lt;/span>(bucketName,&lt;span style="color:#bbb"> &lt;/span>objectName));&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>blob.&lt;span style="color:#4070a0">downloadTo&lt;/span>(Paths.&lt;span style="color:#4070a0">get&lt;/span>(destFilePath));&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I know the name of the bucket, the name of the file, I&amp;rsquo;m going to store the file in the local file system.
So I have all the information needed&amp;hellip; except the project ID within which I deployed my Java cloud function.
So how do I get the project ID, in Java, inside the Cloud Functions environment?&lt;/p></description><content:encoded>
<![CDATA[<p>As I was working with my colleague <a href="https://cloud.google.com/developers/advocates/sara-ford">Sara Ford</a>
on testing the Cloud Functions runtimes for the upcoming &ldquo;second generation&rdquo; of the product,
rebased on the <a href="https://cloud.run/">Cloud Run</a> platform, I wrote a few simple functions for the Java runtime.
In one of those Java functions, I wanted to use Google Cloud Storage, to download a file from a bucket.
I took a look at the existing
<a href="https://github.com/googleapis/google-cloud-java/blob/main/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/objects/DownloadObject.java">sample</a>
to download an object:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Storage<span style="color:#bbb"> </span>storage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>StorageOptions.<span style="color:#4070a0">newBuilder</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">setProjectId</span>(projectId)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">build</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">getService</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>Blob<span style="color:#bbb"> </span>blob<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>storage.<span style="color:#4070a0">get</span>(BlobId.<span style="color:#4070a0">of</span>(bucketName,<span style="color:#bbb"> </span>objectName));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>blob.<span style="color:#4070a0">downloadTo</span>(Paths.<span style="color:#4070a0">get</span>(destFilePath));<span style="color:#bbb">
</span></span></span></code></pre></div><p>I know the name of the bucket, the name of the file, I&rsquo;m going to store the file in the local file system.
So I have all the information needed&hellip; except the project ID within which I deployed my Java cloud function.
So how do I get the project ID, in Java, inside the Cloud Functions environment?</p>
<p>A previous iteration of Cloud Functions had various useful environment variables available, which included the project ID.
So you could retrieve the ID with a <code>System.getenv()</code> call.
However, for various compatibility reasons between the various runtimes,
with the <a href="https://knative.dev/docs/">Knative</a> open source project, that variable disappeared along the road.</p>
<p>However, I know that the project ID is also part of the internal
<a href="https://cloud.google.com/appengine/docs/standard/java/accessing-instance-metadata">compute metadata</a>
that is accessible via a special URL:</p>
<p><a href="http://metadata.google.internal/computeMetadata/v1/project/project-id">http://metadata.google.internal/computeMetadata/v1/project/project-id</a></p>
<p>With that knowledge in mind, I thought I could simply make a quick HTTP request to get that information:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">private</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">getProjectId</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>String<span style="color:#bbb"> </span>projectId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>HttpURLConnection<span style="color:#bbb"> </span>conn<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">null</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>URL<span style="color:#bbb"> </span>url<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>URL(<span style="color:#4070a0">&#34;http://metadata.google.internal/computeMetadata/v1/project/project-id&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>conn<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>(HttpURLConnection)(url.<span style="color:#4070a0">openConnection</span>());<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>conn.<span style="color:#4070a0">setRequestProperty</span>(<span style="color:#4070a0">&#34;Metadata-Flavor&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Google&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>projectId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>String(conn.<span style="color:#4070a0">getInputStream</span>().<span style="color:#4070a0">readAllBytes</span>(),<span style="color:#bbb"> </span>StandardCharsets.<span style="color:#4070a0">UTF_8</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>conn.<span style="color:#4070a0">disconnect</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(Throwable<span style="color:#bbb"> </span>t)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>projectId;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>For the call to work, it is mandatory to set the <code>Metadata-Flavor</code> header that you see above.
I used Java&rsquo;s built-in <code>HttpURLConnection</code> for the job.
There are other HTTP libraries that could&rsquo;ve made the code simpler, but at first,
I didn&rsquo;t want to bring another HTTP client, just for retrieving a simple project meta-information.</p>
<p>I&rsquo;m one of the developers who designed the
<a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework for Java</a>
that is used to craft cloud functions in Java, however, I&rsquo;ve written quite a few functions using Node.js as well.
And in the Node ecosystem, there&rsquo;s actually an NPM module whose responsibility is to retrieve such project metadata.
With the <a href="https://www.npmjs.com/package/gcp-metadata">gcp-metadata</a> module, you can require it and then fetch the project ID with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> gcpMetadata <span style="color:#666">=</span> require(<span style="color:#4070a0">&#39;gcp-metadata&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> projectId <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> gcpMetadata.project(<span style="color:#4070a0">&#39;project-id&#39;</span>);
</span></span></code></pre></div><p>I was surprised I couldn&rsquo;t easily find an equivalent library in Java.
It took me a while to find it, but it actually exists too!
That&rsquo;s the <a href="https://googleapis.dev/java/google-cloud-core/latest/index.html">com.google.cloud:google-cloud-core</a> library!
And it&rsquo;s trivial to use:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.ServiceOptions</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>String<span style="color:#bbb"> </span>projectId<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>ServiceOptions.<span style="color:#4070a0">getDefaultProjectId</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>An extra dependency in my pom.xml, one import and one static method call on <code>ServiceOptions</code>,
and I can get the GCP project ID! So I&rsquo;m now able to pass the project ID to my <code>StorageOptions</code> builder.
But for some reason, I recalled that at times, in some other projects I had written,
I remembered not really needing that project ID information, as the libraries
I was using were smart enough to infer such information from the environment.
Let&rsquo;s look again at the <code>StorageOptions</code> from the beginning.
What if I simply omit the <code>setProjectId()</code> method call?
Lo and behold&hellip; indeed, it was actually not required, and the project ID was inferred, transparently.
So I didn&rsquo;t really need to search for how to retrieve this project ID at all!
And actually, you can further simplify the creation of the StorageOptions down to:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Storage<span style="color:#bbb"> </span>storage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>StorageOptions<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">getDefaultInstance</span>()<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>.<span style="color:#4070a0">getService</span>();<span style="color:#bbb">
</span></span></span></code></pre></div><p>At least, now, I know how to retrieve the project ID in Java,
in case the libraries or the environment are not providing such details on their own!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Introducing Workflows callbacks</title><link>https://glaforge.dev/posts/2021/10/02/introducing-workflows-callbacks/</link><pubDate>Sun, 03 Oct 2021 10:38:29 +0100</pubDate><guid>https://glaforge.dev/posts/2021/10/02/introducing-workflows-callbacks/</guid><description>&lt;p>With &lt;a href="http://cloud.google.com/workflows">Workflows&lt;/a>, developers can easily orchestrate various services together, on Google Cloud or third-party APIs. Workflows &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-new-connectors-workflows">connectors&lt;/a> handle long-running operations of Google Cloud services till completion. And Workflow executions can also wait for time to pass with the built-in &lt;a href="https://cloud.google.com/workflows/docs/reference/stdlib/sys/sleep">&lt;code>sys.sleep function&lt;/code>&lt;/a>, till some computation finishes, or some event takes place. &lt;/p>
&lt;p>But what if you need some user input or some approval in the middle of the workflow execution, like validating automatic text translation? Or an external system like a fulfillment center or an inventory system that is going to notify that products are back in stock? Instead of using a combination of &amp;ldquo;sleep&amp;rdquo; instructions and API polling, now you&amp;rsquo;ll be able to use Workflows callbacks! &lt;/p></description><content:encoded>
<![CDATA[<p>With <a href="http://cloud.google.com/workflows">Workflows</a>, developers can easily orchestrate various services together, on Google Cloud or third-party APIs. Workflows <a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-new-connectors-workflows">connectors</a> handle long-running operations of Google Cloud services till completion. And Workflow executions can also wait for time to pass with the built-in <a href="https://cloud.google.com/workflows/docs/reference/stdlib/sys/sleep"><code>sys.sleep function</code></a>, till some computation finishes, or some event takes place. </p>
<p>But what if you need some user input or some approval in the middle of the workflow execution, like validating automatic text translation? Or an external system like a fulfillment center or an inventory system that is going to notify that products are back in stock? Instead of using a combination of &ldquo;sleep&rdquo; instructions and API polling, now you&rsquo;ll be able to use Workflows callbacks! </p>
<p>With callbacks, the execution of a workflow can wait until it receives a call to a specific callback endpoint. Let&rsquo;s have a look at a concrete example.</p>
<h2 id="case-study-human-validation-of-automated-translation">Case study: human validation of automated translation</h2>
<p>Let&rsquo;s have a look at a concrete example! Machine learning based translations have reached an incredible level of quality, but sometimes, you want a human being to validate the translations produced. Thanks to Workflows callbacks, we can add a human, or an autonomous system, into the loop.</p>
<p>To illustrate this case study, the following diagram will show you a possible implementation of the whole process:</p>
<p><figure>
  <a href="#img-0837dee3eebe07a63b60ef60e115929e">
    <img src="/img/workflows-days/architecture-translation.max-1200x1200.png"
      alt="/img/workflows-days/architecture-translation.max-1200x1200.png"
       />
  </a>
  <figcaption>/img/workflows-days/architecture-translation.max-1200x1200.png</figcaption>
</figure>
<div class="lightbox" id="img-0837dee3eebe07a63b60ef60e115929e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/architecture-translation.max-1200x1200.png"
    alt="/img/workflows-days/architecture-translation.max-1200x1200.png"
     />
  <div class="lightbox-caption">/img/workflows-days/architecture-translation.max-1200x1200.png</div>
</div>
</p>
<ol>
<li>First, the user visits a translation web page. They fill a textarea with the text they want to translate, and click on the translate button.</li>
<li>Clicking on the button will call a Cloud Function that will launch an execution of the workflow. The text to translate is passed as a parameter of the function, and as a parameter of the workflow too.</li>
<li>The text is saved in Cloud Firestore, and the Translation API is called with the input text, and will return the translation, which will be stored in Firestore as well. The translation appears on the web page in real-time thanks to the Firebase SDK.</li>
<li>A step in the workflow creates a callback endpoint (also saved in Firestore), so that it can be called to validate or reject the automatic translation. When the callback endpoint is saved in Firestore, the web page displays validation and rejection buttons.</li>
<li>The workflow now explicitly awaits the callback endpoint to be called. This pauses the workflow execution.</li>
<li>The user decides to either validate or reject the translation. When one of the two buttons is clicked, a Cloud Function is called, with the approval status as parameter, which will in turn call the callback endpoint created by the workflow, also passing the approval status. The workflow resumes its execution, and saves the approval in Firestore. And this is the end of our workflow.</li>
</ol>
<h2 id="creating-a-callback-and-awaiting-incoming-calls">Creating a callback and awaiting incoming calls</h2>
<p>Two new built-in functions are introduced in the standard Workflows library:</p>
<ul>
<li>
<p><a href="https://cloud.google.com/workflows/docs/reference/stdlib/events/create_callback_endpoint"><code>events.create_callback_endpoint</code></a> &mdash; to create and setup the callback endpoint</p>
</li>
<li>
<p><a href="https://cloud.google.com/workflows/docs/reference/stdlib/events/await_callback"><code>events.await_callback</code></a> &mdash; to wait for the callback endpoint to be called</p>
</li>
</ul>
<p>With <code>events.create_callback_endpoint</code> you specify the HTTP method that should be used for invoking the callback endpoint, and you get a dictionary with the URL of that endpoint that you can pass to other systems. And with <code>events.await_callback</code>, you pass the callback endpoint to wait on, pass a timeout defining how long you want to wait, and when the endpoint is called, you get access to the body that was sent to the endpoint.</p>
<p>Let&rsquo;s have a look at the <a href="https://github.com/GoogleCloudPlatform/workflows-demos/blob/master/callback-translation/translation-validation.yaml#L73">YAML definition</a> of our workflow, where we apply those two new functions. First, we&rsquo;re going to create the callback:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">create_callback</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>events.create_callback_endpoint<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">http_callback_method</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;POST&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>callback_details<span style="color:#bbb">
</span></span></span></code></pre></div><p>The callback endpoint is now ready to receive incoming requests via a <code>POST HTTP</code> method, and the details of that endpoint are stored in the <code>callback_details</code> dictionary (in particular, the url key will be associated with the URL of the endpoint).</p>
<p>Next, we pause the workflow, and await the callback with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">await_callback</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>events.await_callback<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">callback</span>:<span style="color:#bbb"> </span>${callback_details}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span><span style="color:#40a070">3600</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>callback_request<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>callback_details</code> from earlier is passed as argument, as well as a timeout in seconds to wait for the callback to be made. When the call is received, all the details of the request are stored in the <code>callback_request</code> dictionary. You then have access to the full HTTP request, including its headers or its body. In case the timeout is reached, a <code>TimeoutError</code> is raised and can be caught by a <code>try /</code> <code>except</code> block.</p>
<h2 id="going-further-and-calling-us-back">Going further and calling us back!</h2>
<p>If you want to have a closer look at the above example, all the <a href="https://github.com/GoogleCloudPlatform/workflows-demos/blob/master/callback-translation/translation-validation.yaml">code for this workflow</a> can be found in the <a href="https://github.com/GoogleCloudPlatform/workflows-demos/">Workflows samples</a> Github repository. And you can follow the details of this <a href="https://cloud.google.com/workflows/docs/tutorial-callbacks-firestore">tutorial</a> to replicate this workflow in your own project. As this is still a preview feature for now, please be sure to <a href="https://docs.google.com/forms/d/e/1FAIpQLSdgwrSV8Y4xZv_tvI6X2JEGX1-ty9yizv3_EAOVHWVKXvDLEA/viewform">request access to this feature</a>, if you want to try it on your own.</p>
<p>For more information on callbacks, be sure to read the <a href="https://cloud.google.com/workflows/docs/creating-callback-endpoints">documentation</a>. To dive deeper into the example above, please checkout the Github <a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/callback-translation">repository</a> of this translation validation sample. Don&rsquo;t hesitate to let us know via Twitter to <a href="http://twitter.com/glaforge">@glaforge</a> what you think of this feature, and how you intend on taking advantage of it in your own workflows!</p>
<p><a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-new-connectors-workflows"></a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Skyrocketing Micronaut microservices into Google Cloud</title><link>https://glaforge.dev/talks/2021/06/25/skyrocketing-micronaut-microservices-into-google-cloud/</link><pubDate>Fri, 25 Jun 2021 14:27:21 +0100</pubDate><guid>https://glaforge.dev/talks/2021/06/25/skyrocketing-micronaut-microservices-into-google-cloud/</guid><description>&lt;p>Instead of spending too much time on infrastructure, take advantage of readily available serverless solutions. Focus on your &lt;a href="https://micronaut.io/">Micronaut&lt;/a> code, and deploy it rapidly as a function, an application, or within a container, on Google Cloud Platform,
with &lt;a href="https://cloud.google.com/functions">Cloud Functions&lt;/a>,
&lt;a href="https://cloud.google.com/appengine">App Engine&lt;/a>,
or &lt;a href="https://cloud.google.com/run">Cloud Run&lt;/a>.&lt;/p>
&lt;p>In this presentation, you’ll discover the options you have to deploy your Micronaut applications and services on Google Cloud. With &lt;a href="https://micronaut.io/launch/">Micronaut Launch&lt;/a>, it’s easy to get started with a template project, and with a few tweaks, you can then push your code to production.&lt;/p></description><content:encoded>
<![CDATA[<p>Instead of spending too much time on infrastructure, take advantage of readily available serverless solutions. Focus on your <a href="https://micronaut.io/">Micronaut</a> code, and deploy it rapidly as a function, an application, or within a container, on Google Cloud Platform,
with <a href="https://cloud.google.com/functions">Cloud Functions</a>,
<a href="https://cloud.google.com/appengine">App Engine</a>,
or <a href="https://cloud.google.com/run">Cloud Run</a>.</p>
<p>In this presentation, you’ll discover the options you have to deploy your Micronaut applications and services on Google Cloud. With <a href="https://micronaut.io/launch/">Micronaut Launch</a>, it’s easy to get started with a template project, and with a few tweaks, you can then push your code to production.</p>
<p>Thanks to its performance, its low memory consumption, and its lightning-fast startup time, Micronaut is particularly well-suited for services that run on serverless solutions.</p>
<script async class="speakerdeck-embed" data-id="d10a8e3b48244eab95ea9bd1c81547b5" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Introducing New Connectors for Workflows</title><link>https://glaforge.dev/posts/2021/04/27/introducing-new-connectors-for-workflows/</link><pubDate>Tue, 27 Apr 2021 13:52:35 +0100</pubDate><guid>https://glaforge.dev/posts/2021/04/27/introducing-new-connectors-for-workflows/</guid><description>&lt;p>&lt;a href="http://cloud.google.com/workflows">Workflows&lt;/a> is a service to orchestrate not only Google Cloud services, such as Cloud Functions,  Cloud Run, or machine learning APIs, but also external services. As you might expect from an orchestrator, Workflows allows you to define the flow of your business logic, as steps, in a YAML or JSON definition language, and provides an execution API and UI to trigger workflow executions. You can read more about the benefits of Workflows in our &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/better-service-orchestration-workflows">previous article&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p><a href="http://cloud.google.com/workflows">Workflows</a> is a service to orchestrate not only Google Cloud services, such as Cloud Functions,  Cloud Run, or machine learning APIs, but also external services. As you might expect from an orchestrator, Workflows allows you to define the flow of your business logic, as steps, in a YAML or JSON definition language, and provides an execution API and UI to trigger workflow executions. You can read more about the benefits of Workflows in our <a href="https://cloud.google.com/blog/topics/developers-practitioners/better-service-orchestration-workflows">previous article</a>.</p>
<p>We are happy to announce new <a href="https://cloud.google.com/workflows/docs/connectors">connectors</a> for Workflows, which simplify calling Google Cloud services and APIs. </p>
<p>The first documented connectors offered in preview when Workflows was launched in General Availability were:</p>
<ul>
<li>Cloud Tasks</li>
<li>Compute Engine</li>
<li>Firestore</li>
<li>Pub/Sub</li>
<li>Secret Manager</li>
</ul>
<p>The newly unveiled connectors are:</p>
<ul>
<li>BigQuery</li>
<li>Cloud Build</li>
<li>Cloud Functions</li>
<li>Cloud Scheduler</li>
<li>Google Kubernetes Engine</li>
<li>Cloud Natural Language API</li>
<li>Dataflow</li>
<li>Cloud SQL</li>
<li>Cloud Storage</li>
<li>Storage Transfer Service</li>
<li>Cloud Translation</li>
<li>Workflows &amp; Workflow Executions</li>
</ul>
<p>In addition to simplifying Google Cloud service calls (no need to manually tweak the URLs to call) from workflow steps, connectors also handle errors and <a href="https://cloud.google.com/workflows/docs/connectors">retries</a>, so you don&rsquo;t have to do it yourself. Furthermore, they take care of APIs with <a href="https://cloud.google.com/workflows/docs/connectors#long-running_operations">long-running operations</a>, polling the service for a result when it&rsquo;s ready, with a back-off approach, again so you don&rsquo;t have to handle this yourself.</p>
<p>Let&rsquo;s take a look at some concrete examples on how connectors help. </p>
<h2 id="creating-a-compute-engine-vm-with-a-rest-api-call">Creating a Compute Engine VM with a REST API call</h2>
<p>Imagine you want to create a Compute Engine Virtual Machine (VM) in a specified project and zone. You can do this by crafting an HTTP POST request with the proper URL, body, and OAuth2 authentication using the Compute Engine API&rsquo;s <a href="https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert">instances.insert</a> method as shown in <a href="https://github.com/GoogleCloudPlatform/workflows-demos/blob/master/connector-compute/create-vm.yaml">create-vm.yaml</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[args]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">init</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;europe-west1-b&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">machineType</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;e2-small&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">instanceName</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;${args.instanceName}&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">insert_machine</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${&#34;https://compute.googleapis.com/compute/v1/projects/&#34; + project + &#34;/zones/&#34; + zone + &#34;/instances&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>${instanceName}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">machineType</span>:<span style="color:#bbb"> </span>${&#34;zones/&#34; + zone + &#34;/machineTypes/&#34; + machineType}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">disks</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>- <span style="color:#062873;font-weight:bold">initializeParams</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">sourceImage</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;projects/debian-cloud/global/images/debian-10-buster-v20201112&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">boot</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">autoDelete</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">networkInterfaces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>- <span style="color:#062873;font-weight:bold">network</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;global/networks/default&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>This works but it is quite error prone to construct the right URL with the right parameters and authentication mechanism. You also need to poll the instance status to make sure it&rsquo;s running before concluding the workflow:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">get_instance</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>${&#34;https://compute.googleapis.com/compute/v1/projects/&#34; + project + &#34;/zones/&#34; + zone + &#34;/instances/&#34; + instanceName}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>getInstanceResult<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">assert_running</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb"> </span>${getInstanceResult.body.status == &#34;RUNNING&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>end<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>sleep<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">sleep</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>sys.sleep<span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># Polling through sleep</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">seconds</span>:<span style="color:#bbb"> </span><span style="color:#40a070">3</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>get_instance<span style="color:#bbb">
</span></span></span></code></pre></div><p>Note that even the HTTP GET call above could fail and it&rsquo;d be better to wrap the call in a retry logic. </p>
<h2 id="creating-a-compute-engine-vm-with-the-workflows-compute-connector">Creating a Compute Engine VM with the Workflows compute connector</h2>
<p>In contrast, let&rsquo;s now create the same VM with the compute connector dedicated to Compute Engine as shows in <a href="https://github.com/GoogleCloudPlatform/workflows-demos/blob/master/connector-compute/create-vm-connector.yaml">create-vm-connector.yaml</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[args]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">init</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;europe-west1-b&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">machineType</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;e2-small&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">instanceName</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;${args.instanceName}&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">insert_machine</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.compute.v1.instances.insert<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${project}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span>${zone}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>${instanceName}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">machineType</span>:<span style="color:#bbb"> </span>${&#34;zones/&#34; + zone + &#34;/machineTypes/&#34; + machineType}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">disks</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>- <span style="color:#062873;font-weight:bold">initializeParams</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">sourceImage</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;projects/debian-cloud/global/images/debian-10-buster-v20201112&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">boot</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">autoDelete</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">networkInterfaces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span>- <span style="color:#062873;font-weight:bold">network</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;global/networks/default&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>The overall structure and syntax is pretty similar, but this time, we didn&rsquo;t have to craft the URL ourselves, nor did we have to specify the authentication method. Although it&rsquo;s invisible in this YAML declaration, error handling and retry logic are handled by Workflows directly, unlike the first example where you have to handle it yourself.</p>
<h2 id="transparent-waiting-for-long-running-operations">Transparent waiting for long-running operations</h2>
<p>Some operations from cloud services are not instantaneous and can take a while to execute. A synchronous call to such operations will return immediately with an object that indicates the status of that long-running operation. </p>
<p>From a workflow execution, you might want to call a long-running operation and move to the next step only when that operation has finished. In the standard REST approach, you have to check at regular intervals if the operation has terminated or not. To save you from the tedious work of iterating and waiting, connectors take care of this for you! </p>
<p>Let&rsquo;s illustrate this with another example with Compute Engine. Stopping a VM can take a while. A <a href="https://cloud.google.com/compute/docs/reference/rest/v1/instances/stop">request</a> to the Compute Engine REST API to stop a VM returns an <a href="https://cloud.google.com/compute/docs/reference/rest/v1/instances/stop#response-body">object</a> with a status field that indicates whether the operation has completed or not.</p>
<p>The Workflows compute connector and its <a href="https://cloud.google.com/compute/docs/reference/rest/v1/instances/stop">instances.stop</a> operation will appropriately wait for the stop of the VM &ndash; no need for you  to keep checking its status until the VM stops. It greatly simplifies your workflow definition as shown in <a href="https://github.com/GoogleCloudPlatform/workflows-demos/blob/master/connector-compute/create-stop-vm-connector.yaml">create-stop-vm-connector.yaml</a>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[args]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">init</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;europe-west1-b&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">machineType</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;e2-small&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">instanceName</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;${args.instanceName}&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">stop_machine</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.compute.v1.instances.stop<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">instance</span>:<span style="color:#bbb"> </span>${instanceName}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${project}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span>${zone}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#60a0b0;font-style:italic"># Optional connector parameters</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">connector_params</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span><span style="color:#40a070">100</span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># total time is 100s</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">polling_policy</span>:<span style="color:#bbb">  </span><span style="color:#60a0b0;font-style:italic"># optional polling parameters for LRO polling.</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">initial_delay</span>:<span style="color:#bbb"> </span><span style="color:#40a070">1</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">multiplier</span>:<span style="color:#bbb"> </span><span style="color:#40a070">1.25</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">assert_terminated</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>assert_machine_status<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">expected_status</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;TERMINATED&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${project}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span>${zone}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">instanceName</span>:<span style="color:#bbb"> </span>${instanceName}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">assert_machine_status</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[expected_status, project, zone, instanceName]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">get_instance</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>googleapis.compute.v1.instances.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">instance</span>:<span style="color:#bbb"> </span>${instanceName}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">project</span>:<span style="color:#bbb"> </span>${project}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">zone</span>:<span style="color:#bbb"> </span>${zone}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>instance<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">compare</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb"> </span>${instance.status == expected_status}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>end<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>- <span style="color:#062873;font-weight:bold">fail</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">raise</span>:<span style="color:#bbb"> </span>${&#34;Expected VM status is &#34; + expected_status + &#34;. Got &#34; + instance.status + &#34; instead.&#34;}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Note that we still use the <a href="https://cloud.google.com/compute/docs/reference/rest/v1/instances/get">instances.get</a> operation in a subworkflow to check that the VM is indeed TERMINATED but this is nice-to-have as <a href="https://cloud.google.com/compute/docs/reference/rest/v1/instances/stop">instances.stop</a> already waits for the VM to stop before returning.</p>
<p>In connector, users can set a timeout field, which is the total wait time for this connector call. All of the retries and polling logic is hidden. Now, compare this to <a href="https://github.com/GoogleCloudPlatform/workflows-demos/blob/master/connector-compute/stop-vm.yaml">stop-vm.yaml</a> where the workflow stops the VM without the connector. You can see that the YAML is longer and the logic is more complicated with HTTP retry policy for the stop call and also the polling logic to make sure the VM is actually stopped.</p>
<h2 id="increased-reliability-through-connector-retries">Increased reliability through connector retries</h2>
<p>Even the best services can have momentary outages due to traffic spikes or network issues. Google Cloud Pub/Sub has an SLA of <a href="https://uptime.is/99.95">99.95</a>, which means no more than 43s of downtime per day on average, or under 22 minutes per month. Of course, most products routinely outperform their SLAs by a healthy margin. What if you want strong assurances your workflow won&rsquo;t fail if products remain within their SLAs? Since Workflows connectors retry operations over a period of several minutes, even if there is an outage of several minutes, the operation will succeed and so will the workflow.</p>
<h2 id="lets-connect">Let&rsquo;s connect!</h2>
<p>To learn more about <a href="https://cloud.google.com/workflows/docs/connectors">connectors</a>, have a look at some of our <a href="https://github.com/GoogleCloudPlatform/workflows-samples/tree/main/src/connectors">workflows-samples</a> repo, which show you how to interact with Compute Engine, Cloud Pub/Sub, Cloud Firestore, and Cloud Tasks. You can find the samples described in this blog post in <a href="https://github.com/GoogleCloudPlatform/workflows-demos/tree/master/connector-compute">workflows-demos/connector-compute</a>.</p>
<p>This is the initial set of connectors; there are many more Google Cloud products for which we will be creating dedicated connectors. We&rsquo;d love to hear your thoughts about which connectors we should prioritize and focus on next (fill this <a href="https://forms.gle/HKYn83bhDKWFSDQr7">form</a> to tell us). Don&rsquo;t hesitate to let us know via Twitter to <a href="https://twitter.com/meteatamel">@meteatamel</a> and <a href="http://twitter.com/glaforge">@glaforge</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless: panacea or not?</title><link>https://glaforge.dev/talks/2021/03/07/serverless-panacea-or-not/</link><pubDate>Sun, 07 Mar 2021 22:36:12 +0100</pubDate><guid>https://glaforge.dev/talks/2021/03/07/serverless-panacea-or-not/</guid><description>&lt;p>At &lt;a href="https://www.devday.be/Sessions/Details/94?slug=the-serverless-panacea-or-not">DevDay Belgium&lt;/a>, a few months ago, I had the pleasure to give a keynote on the theme of &amp;ldquo;serverless&amp;rdquo;. Let me share with you this talk today!&lt;/p>
&lt;p>&lt;strong>The Serverless Panacea&amp;hellip; Or Not?&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>The term &amp;ldquo;serverless&amp;rdquo; has become a trendy buzzword: if you don&amp;rsquo;t have the checkbox ticked, you&amp;rsquo;re not cool anymore. Really?&lt;/p>
&lt;p>Spoiler alert: There may be servers involved in serverless solutions. It&amp;rsquo;s not just about function-as-a-service. And it&amp;rsquo;s actually more complicated than it may seem!&lt;/p></description><content:encoded>
<![CDATA[<p>At <a href="https://www.devday.be/Sessions/Details/94?slug=the-serverless-panacea-or-not">DevDay Belgium</a>, a few months ago, I had the pleasure to give a keynote on the theme of &ldquo;serverless&rdquo;. Let me share with you this talk today!</p>
<p><strong>The Serverless Panacea&hellip; Or Not?</strong></p>
<blockquote>
<p>The term &ldquo;serverless&rdquo; has become a trendy buzzword: if you don&rsquo;t have the checkbox ticked, you&rsquo;re not cool anymore. Really?</p>
<p>Spoiler alert: There may be servers involved in serverless solutions. It&rsquo;s not just about function-as-a-service. And it&rsquo;s actually more complicated than it may seem!</p>
<p>But first, let&rsquo;s come back to the basics: what is serverless exactly, where does it come from, what are its characteristics? Then, beyond the definition, we&rsquo;ll discuss the challenges, and the risks associated with serverless architectures. Eventually, going further, we&rsquo;ll think about where serverless is heading in the near future.</p></blockquote>
<p>You can find the slides below:</p>
<script async class="speakerdeck-embed" data-id="cb3b04200d7b4b94a0b22dfd9a401d0a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>And you can find the video below. Further down, I&rsquo;ll detail each slide of my keynote:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/EkE3UfzVO8o?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Today, I&rsquo;d like to tell you about Serverless.
As a Developer Advocate, for Google Cloud, that&rsquo;s products and the topic I&rsquo;m focusing on.
Serverless is a big buzzword of the day, but is it a real panacea or not?</p>
<p>Like Obelix, I fell into the magic serverless potion a long time ago&hellip;
I started playing with Google App Engine Java in 2009, even before it was officially announced by Google at the Google I/O conference.
Google team reached out to me, to work together in stealth mode, to ensure that alternative JVM languages would run fine on their upcoming Java flavor for App Engine (I&rsquo;m the co-founder of the Groovy language)</p>
<p>I couldn&rsquo;t imagine then that I&rsquo;d be starting to work for Google 7 years later. And that I would focus on those serverless solutions!
It was still called Platform-as-a-Service, as the term &ldquo;serverless&rdquo; wasn&rsquo;t invented yet (although they are pretty similar)
And I&rsquo;ve been a big fan and big user of App Engine ever since.</p>
<p>After this brief personal story with Obelix and Google, let&rsquo;s actually start with a little bit of background and history.
This is my version of the story, so don&rsquo;t take it too seriously.</p>
<p>At the beginning, humans created the server. 
A machine on which you could run various programs and apps.
Well, we also created the internet, of course, otherwise we couldn&rsquo;t connect our web apps to our users.
If you have a few users, a single server may suffice.
But you know how it goes, with the spread of the web and internet, we now have billions of users, and millions of servers.
Things kinda got complicated, and we introduced lots of hard concepts around distributed microservices, replicated databases.
We even coined theorems, like the CAP theorem, for Consistency, Availability, Partitioning. But you can only pick 2.</p>
<p>Humans invented the cloud, in order to avoid dealing with the physical world.
But there are still databases, servers, or virtual machines to manage.
However, you don&rsquo;t have to get your hands dirty with the ethernet cables, changing the failing hard-drives, upgrading to the latest CPU and RAM.
Usually, it&rsquo;s the cloud provider that has folks that wake up in the middle of the night to upgrade those things.
You can sleep a little bit better at night, even if your boss may still call you at 3am because your app is misbehaving.</p>
<p>To focus on the code, and to avoid the complexity of managing servers, provisioning clusters, configuring networking, fine-tuning databases, even in the cloud&hellip; humans came up with the concept of serverless! 
Here is a picture of the latest Google datacenter for serverless! Look, no servers!
Well, I&rsquo;m kidding, of course, there are always servers around!</p>
<p>Even if the word serverless wasn&rsquo;t there yet, it all started with Platform-as-a-Service, with App Engine and Heroku.
The promise was &ldquo;give us your code or your app, and we&rsquo;ll run it for you&rdquo;.
The hardware management aspect was already there. Scaling was also handled by the platform.
The pricing as well was proportional to the usage of the resources.</p>
<p>You also have BaaS &mdash; Backend as a Service
It&rsquo;s pretty similar to PaaS actually.
It comes with batteries-included. You focus on the frontend, and all the backend is provided for you.
Parse and Firebase are two good examples. Facebook kinda abandoned Parse into open-source land.
But Firebase is still around and integrates more and more with the various Google Cloud Platform services.
So you can have hosting of static assets, a datastore to save your information, some kind of runtime environment to run your business logic.
And tons of other services, for authentication, mobile crash analysis, performance testing, analytics, and more.</p>
<p>PaaS, then BaaS, and also FaaS: Functions-as-a-service.
With a FaaS solution, your unit of work, of deployment, becomes a small, granular function.
This concept was popularized by Amazon Lambda.
And often, even still today, people tend to confuse FaaS with Serverless.
But FaaS is really just one facet of the Serverless ecosystem. Like PaaS or BaaS.</p>
<p>Another interesting facet of serverless is the Container-as-a-Service approach, with containerized workloads.
Instead deploying apps or functions, you&rsquo;re deploying containers.
Put anything you want inside a container. 
That&rsquo;s the approach that Google took with its Cloud Run container service.
You don&rsquo;t have the complexity of Kubernetes, but you can run your container easily in the cloud, in a fully managed environment.</p>
<p>Right, so I described a bit the history that lead us to serverless and its various facets, and some of the serverless products that are available today, but let&rsquo;s take some time to give a proper definition of what serverless is.</p>
<p>For me, Serverless is the easiest way to get an idea to production in a minimal amount of time.
As a developer, you work on some code, and then you deploy it. That&rsquo;s it! Really!</p>
<p>The term serverless was coined around 2010 by someone called Ken Elkabany, who created the PiCloud computing platform.
Compared to Heroku and App Engine which came earlier, and focusing on implementing web stacks in their cloud datacenters, PiCloud was more generic and supported different kinds of workloads, not just serving web requests.
The catchy term came from the fact that they were actually selling a service, rather than selling or renting servers, machines, VMs, to their customers.</p>
<p>There are 2 ways to think about Serverless: there&rsquo;s the Operational model, and the Programming model.</p>
<p>Operational model:</p>
<ul>
<li>Fully managed, </li>
<li>Automatic scaling, </li>
<li>Pay as you go</li>
</ul>
<p>Programming model</p>
<ul>
<li>Service based, </li>
<li>Event driven, </li>
<li>Stateless</li>
</ul>
<p>There&rsquo;s no provisioning of clusters, servers, instances, VMs or anything.
It&rsquo;s all handled by the platform, for you. Just give your code, your app, your function.
It&rsquo;s a fully managed environment. Security patches are applied automatically. 
You remember specter and meltdown recently? They&rsquo;ve been mitigated transparently and rapidly for customers by Google Cloud. No wake up call in the night.
Your apps will scale automatically, from 0 to 1 instance, from 1 to n instances, and from n down to 1, as well as back to zero.
Tracking the CPU load, memory usage, number of incoming requests, with some magic formula, serverless platforms are able to scale up and down your services.
Without you having to worry about it. The cloud provider is handling that for you.</p>
<p>In terms of pricing, it&rsquo;s a Pay-as-you-go cost model.
It goes hand in hand with automatic scaling.
If there&rsquo;s no traffic, you pay zero.
If there&rsquo;s twice more traffic than usual, you pay proportionately.
And if the load goes back to zero, the instances serving your app are decommissioned, and again you pay zero.</p>
<p>Now onto the programming model.
More and more, we&rsquo;re transitioning from building big monoliths into orchestrating smaller services, or microservices.
It has its challenges though, but with smaller services, your teams can develop them more independently, scale them differently, or event deploy them with different life cycles.</p>
<p>Since you have some more loosely coupled services, they tend to react to incoming events from your system or from the cloud (for example a notification of a new file in cloud storage, a new line in a reactive datastore like Cloud Firestore, a message in a message bus like Pub/Sub), 
Your services usually communicate asynchronously, to stay properly decoupled.
But the more asynchronous you are, the harder things are to operate and monitor, when business logic spans several services, you have to figure out what&rsquo;s the current status of that workflow across those services.</p>
<p>Another important aspect of the fact that services can scale up and down and back to zero is that there&rsquo;s no guarantee that you&rsquo;re going to hit the same server all the time. 
So you can&rsquo;t be certain that some data that would be cached is still there.
You have to program defensively to ensure that any fresh instance of your app is able to cope with any incoming request.
State is pretty much an enemy of scaling. So the more stateless you can be, the better it is.</p>
<ul>
<li>Compute, </li>
<li>Data Analytics</li>
<li>ML &amp; AI</li>
<li>Database &amp; Storage</li>
<li>Smart assistants &amp; chat</li>
<li>DevOps</li>
<li>Messaging</li>
</ul>
<p>We&rsquo;ve been speaking about serverless compute, but serverless is not just about compute.
You could consider that anything that is fully managed, that offers a pay-as-you-go cost model, that is a service in the cloud, then it&rsquo;s also all serverless, since you don&rsquo;t have to worry about the infrastructure and the scaling.
There are great examples of this in Google Cloud, for example BigQuery which is a fully-managed, serverless data warehouse and analytics platform. You pay proportionally to the amount of data your queries are going through! Not for the storage, not for the running servers, etc. 
But let&rsquo;s get back to serverless compute.</p>
<p>Serverless sounds pretty cool, right?
But there are also challenges, compared to running a good old monolith on your on-premises server.
We&rsquo;ve already given some hints of some of the challenges.
In particular, I&rsquo;d like to spend some time to tell you about four key aspects:</p>
<ul>
<li>The lock-in factor</li>
<li>The infamous cold starts</li>
<li>Cost controls</li>
<li>And the mess of spaghetti microservices</li>
</ul>
<p>PaaS or BaaS often come with batteries-included.
They have built-in APIs or databases, which are super convenient for developers.
As a developer, you are much more productive, because you don&rsquo;t have to wire things up, configure third-party services. The choice is already made for you.
But I&rsquo;m seeing those batteries more and more being externalized, as their own standalone products. 
Google Cloud has externalized things like its NoSQL database, its Pub/Sub message hub, its scheduler, its task handling capabilities. Before those services were actually part of the Platform-as-a-Service.</p>
<p>However great having built-in powerful batteries is, often these are proprietary and specific to that platform.
You end up being locked to the platform you&rsquo;re building upon.
It can be a choice, as long as you are aware of it.
It&rsquo;s a trade-off between portability and time-to-market.
You might still be tied to those products, but at least, you can still move the business logic around, if those services are externalized. 
And you can create a level of indirection to be able, some day, potentially, to move away from those service dependencies if needed.</p>
<p>A common issue you hear about in serverless-land is the infamous &ldquo;Cold Start&rdquo;.
Since you can scale to zero, it means there&rsquo;s currently no server, instance, or clone, to serve an incoming request.
So what happens? The cloud provider has to reinitialize, re-instantiate, re-hydrate some kind of server, VM, or container.
Additionally, the underlying language runtime has to startup as well, initializing its internal data structures.
Not only that, but your app also needs to get started too.
So you&rsquo;d better try to minimize the time your apps need to be ready to serve their first request. Since you have control over this part.</p>
<p>There are workarounds, like pinging your service at regular intervals to keep it warm, but it&rsquo;s a bit of a hack, or even an anti-pattern.
Depending on the pricing, that might mean you&rsquo;re paying for nothing potentially, for something that&rsquo;s sitting idle.
Some platforms provide some knobs that you can play with like &ldquo;min instances&rdquo; or &ldquo;provisioned instances&rdquo;, usually at a lower price.
For instance, on Google Cloud Functions or Cloud Run, you can specify that you want a certain minimum number of instances that are already warm, and ready to serve, and that are cheaper.</p>
<p>I mention a minimum number of instances, but what about the notion of maximum number of instances?
It&rsquo;s actually an important idea. 
With a platform that auto-scales transparently, that can spin up as many instances to serve increased traffic, it also means that your costs are going to increase just as much! 
So in order to bound your budget to a known quantity, rather than burning your money with all your hot instances, you can cap the number of instances that will serve your content. The service may be a bit degraded when you reach that limit, as latency will likely increase, but at least, your budget doesn&rsquo;t go through the roof!
That&rsquo;s why Google Cloud Platform introduced that notion of capping the number of instances running your functions, apps or containers in its serverless product: to have more visibility and control around costs.</p>
<p>The last challenge I&rsquo;d like to mention is spaghetti services.
It&rsquo;s so easy to write many functions and services on a serverless platform.
One service does one thing and does it well, right?
But after a while, you end up with a spaghetti of microservices. A big mess.
It becomes very complicated to see what invokes what. 
Hard for monitoring and observability to really figure out what happened, when one microservice starting somehow to misbehave completely ruin your clockwork architecture.
And you know: monoliths aren&rsquo;t that bad, actually.
Don&rsquo;t start right away with writing the smallest unit of work possible. 
Pay attention to how you split the big monolith into microservices.
Otherwise, you&rsquo;ll end up with that big plate of spaghetti. 
There are good articles on when and how to split monolith, but it&rsquo;s not a simple rule of thumb answer.</p>
<p>So what does the future hold for serverless?
I believe that the highlights will be about:</p>
<ul>
<li>Openness</li>
<li>Containers</li>
<li>Glue</li>
<li>Edge</li>
<li>Machine Learning</li>
</ul>
<p>Let&rsquo;s start with open, and openness. That&rsquo;s open like in open source!
We want to avoid lock-in. We want portability.
For instance, the platforms rely on open source software for sure, but the platforms themselves can be open source too.
If you look at Google&rsquo;s Cloud Run, it&rsquo;s actually based on the Knative open source Kubernetes platform.
So you&rsquo;re not locked in Google Cloud when you&rsquo;re using Cloud Run. You can move your workload, your app, on a Knative compatible platform from another cloud provider, or even on-premises, on your own infrastructure.
I worked on the Java Cloud Functions runtime, and it is also available as open source. So you can deploy your functions in Google Cloud, but you can also run your functions elsewhere too, in a hybrid cloud scenario, or even just locally on your machine for greater developer experience and a tighter development feedback loop.
Also, the way you build your services from sources, this can be made more open too. 
For instance, Heroku and Google Cloud partnered together on Cloud Native Buildpacks, it helps you transform your application source code into images that can run on any cloud.
Really, it&rsquo;s all about portability and avoiding lock-in, by making things as open as possible.</p>
<p>As I&rsquo;m mentioning Cloud Native Buildpacks, and the fact it builds portable containers for your app&rsquo;s source code, notice that we&rsquo;re speaking of containers.
Why containers, you may ask. 
With things like platform or function as a service, you are pushing apps on the platform runtime. But you may be limited in terms of language runtime, or library, or binary that you can run there or bundle. If you&rsquo;re using an esoteric language, or need some special software installed, perhaps you won&rsquo;t be able to run your app there.
Instead, if you could put everything you need in a box, and if the cloud could just run that box for you, then you can do pretty much anything.
That&rsquo;s why we&rsquo;re using containers more and more. And that&rsquo;s also why Google Cloud released Cloud Run, to run your containers, but serverlessly, with any runtime, library or language that you want, without limitations.
So I&rsquo;m seeing more containers in the future.</p>
<p>You remember my plate of spaghettis? 
To orchestrate your services, to observe and monitor them, to track that they are communicating properly, asynchronously, you&rsquo;ll need more tools to ensure that it all runs fine in the cloud. That&rsquo;s why I&rsquo;m seeing more tools like Google Cloud Tasks, Cloud Scheduler, Cloud Workflows, and in the Azure and AWS worlds, you have things like Logic App or Step Functions.
You also have various messaging busses, like Google Cloud Pub/Sub, Amazon SQS, Azure Service Bus.
And in the Kubernetes world, we&rsquo;ve seen service meshes emerge as a key architectural pattern.
A monolith is much simpler to develop &amp; operate, but as you move to a microservice architecture, those glue services will be needed more and more.
So I see more glue in the future!</p>
<p>Recently, CloudFlare released a product called CloudFlare Workers.
It&rsquo;s using the V8 JavaScript engine, and its isolates concept to run your functions, in a sandboxed manner.
There are two very interesting aspects to me in this new product.
First of all, that&rsquo;s the idea of having your serverless functions run at the edge of the network. 
Not deep in a handful of big data centers. Instead, those functions are as close to the users as possible.
So the latency is really minimal.
Secondly, to further reduce latency, there&rsquo;s a great innovation that almost completely eliminates cold starts!
CloudFlare actually starts warming up your function as soon as the SSL handshake is requested when you invoke the function via HTTPS, although normally the whole handshake operation has to be done first, and the call routed to your function, before really starting.
So that&rsquo;s a really great optimization! And we&rsquo;ll probably see more stuff moving to the edge of the cloud infrastructure.</p>
<p>Lastly, looking even further in the future, I&rsquo;m curious to see how machine learning will play a role in the serverless offering of cloud providers.
In particular, you still have to specify a VM or instance size, its memory or CPU. Some would say it&rsquo;s not very serverless, since servers are supposed to be abstracted away.
In Google Cloud, for example, we have what we call a &ldquo;clone scheduler&rdquo; that is responsible for creating a new instance of your function or app, depending on various factors, like CPU usage, memory usage, number of incoming queries, etc. 
There&rsquo;s some magical calculation that figures out how and when to spin up a new instance.</p>
<p>Google recently automated its datacenter thanks to Machine Learning, reducing its power consumption by 40%! (Power Usage Efficiency)
I can imagine a future where Machine Learning is used to further upsize or downsize the underlying machines running your serverless code, and provision the right amount of resources, to reduce latency, CPU usage, etc.
So let&rsquo;s see what the future holds for Serverless!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Orchestrating the Pic-a-Daily serverless app with workflows</title><link>https://glaforge.dev/posts/2021/02/13/orchestrating-the-pic-a-daily-serverless-app-with-workflows/</link><pubDate>Sat, 13 Feb 2021 19:15:05 +0100</pubDate><guid>https://glaforge.dev/posts/2021/02/13/orchestrating-the-pic-a-daily-serverless-app-with-workflows/</guid><description>&lt;p>Over the past year, we (&lt;a href="https://twitter.com/meteatamel">Mete&lt;/a> and &lt;a href="https://twitter.com/glaforge">Guillaume&lt;/a>) have developed a picture sharing application, named Pic-a-Daily, to showcase Google Cloud serverless technologies such as Cloud Functions, App Engine, and Cloud Run. Into the mix, we&amp;rsquo;ve thrown a pinch of Pub/Sub for interservice communication, a zest of Firestore for storing picture metadata, and a touch of machine learning for a little bit of magic.&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-67de80a884cfbad321d40341bd2b97ab">
&lt;img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/1_Shqfx7L.max-1400x1400.png"
alt="/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png"
/>
&lt;/a>
&lt;figcaption>/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-67de80a884cfbad321d40341bd2b97ab">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/1_Shqfx7L.max-1400x1400.png"
alt="/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png"
/>
&lt;div class="lightbox-caption">/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>We also created a &lt;a href="https://codelabs.developers.google.com/serverless-workshop/">hands-on workshop&lt;/a> to build the application, and &lt;a href="https://speakerdeck.com/meteatamel/pic-a-daily-serverless-workshop">slides&lt;/a> with explanations of the technologies used. The workshop consists of codelabs that you can complete at your own pace. All the code is open source and available in a &lt;a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop">GitHub repository&lt;/a>. &lt;/p></description><content:encoded>
<![CDATA[<p>Over the past year, we (<a href="https://twitter.com/meteatamel">Mete</a> and <a href="https://twitter.com/glaforge">Guillaume</a>) have developed a picture sharing application, named Pic-a-Daily, to showcase Google Cloud serverless technologies such as Cloud Functions, App Engine, and Cloud Run. Into the mix, we&rsquo;ve thrown a pinch of Pub/Sub for interservice communication, a zest of Firestore for storing picture metadata, and a touch of machine learning for a little bit of magic.</p>
<p><figure>
  <a href="#img-67de80a884cfbad321d40341bd2b97ab">
    <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/1_Shqfx7L.max-1400x1400.png"
      alt="/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png"
       />
  </a>
  <figcaption>/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png</figcaption>
</figure>
<div class="lightbox" id="img-67de80a884cfbad321d40341bd2b97ab">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/1_Shqfx7L.max-1400x1400.png"
    alt="/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png"
     />
  <div class="lightbox-caption">/img/picadailly-workflows/1_Shqfx7L.max-1400x1400.png</div>
</div>
</p>
<p>We also created a <a href="https://codelabs.developers.google.com/serverless-workshop/">hands-on workshop</a> to build the application, and <a href="https://speakerdeck.com/meteatamel/pic-a-daily-serverless-workshop">slides</a> with explanations of the technologies used. The workshop consists of codelabs that you can complete at your own pace. All the code is open source and available in a <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop">GitHub repository</a>. </p>
<h2 id="initial-event-driven-architecture">Initial event-driven architecture</h2>
<p>The Pic-a-Daily application evolved progressively. As new services were added over time, a loosely-coupled, event-driven architecture naturally emerged, as shown in this architecture diagram:</p>
<p><figure>
  <a href="#img-eb1f8c5e07f687a263764ee54504284b">
    <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/2_XMbrwvr.max-1100x1100.png"
      alt="/img/picadailly-workflows/2_XMbrwvr.max-1100x1100.png"
       />
  </a>
  <figcaption>/img/picadailly-workflows/2_XMbrwvr.max-1100x1100.png</figcaption>
</figure>
<div class="lightbox" id="img-eb1f8c5e07f687a263764ee54504284b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/2_XMbrwvr.max-1100x1100.png"
    alt="/img/picadailly-workflows/2_XMbrwvr.max-1100x1100.png"
     />
  <div class="lightbox-caption">/img/picadailly-workflows/2_XMbrwvr.max-1100x1100.png</div>
</div>
</p>
<p>To recap the event-driven flow:</p>
<ol>
<li>Users upload pictures on an App Engine web frontend. Those pictures are stored in a Google Cloud Storage bucket, which triggers file creation and deletion events, propagated through mechanisms such as Pub/Sub and Eventarc. </li>
<li>A Cloud Function (Image analysis) reacts to file creation events. It calls the Vision API to assign labels to the picture, identify the dominant colors, and check if it&rsquo;s a picture safe to show publicly. All this picture metadata is stored in Cloud Firestore. </li>
<li>A Cloud Run service (Thumbnail service) also responds to file creation events. It generates thumbnails of the high-resolution images and stores them in another bucket. </li>
<li>On a regular schedule triggered by Cloud Scheduler, another Cloud Run service (Collage services) creates a collage from thumbnails of the four most recent pictures. </li>
<li>Last but not least, a third Cloud Run service (Image garbage collector) responds to file deletion events received through <a href="https://cloud.google.com/blog/products/serverless/eventarc-is-ga">(recently generally available) Eventarc</a>. When a high-resolution image is deleted from the pictures bucket, this service deletes the thumbnail and the Firestore metadata of the image.</li>
</ol>
<p>These services are loosely coupled and take care of their own logic, in a smooth choreography of events. They can be scaled independently. There&rsquo;s no single point of failure, since services can continue to operate even if others have failed. Event-based systems can be extended beyond the current domain at play by plugging in other events and services to respond to them.</p>
<p>However, monitoring such a system in its entirety usually becomes complicated, as there&rsquo;s no centralized place to see where we&rsquo;re at in the current business process that spans all the services. Speaking of business processes, it&rsquo;s harder to capture and make sense of the flow of events and the interplay between services. Since there&rsquo;s no global vision of the processes, how do we know if a particular process or transaction is successful or not? And when failures occur, how do we deal properly and explicitly with errors, retries, or timeouts?</p>
<p>As we kept adding more services, we started losing sight of the underlying &ldquo;business flow&rdquo;. It became harder to isolate and debug problems when something failed in the system. That&rsquo;s why we decided to investigate an orchestrated approach.</p>
<h2 id="orchestration-with-workflows">Orchestration with Workflows</h2>
<p><a href="https://cloud.google.com/workflows">Workflows</a> recently became generally available. It offered us a great opportunity to re-architect our application and use an orchestration approach, instead of a completely event-driven one. In orchestration, instead of microservices responding to events, there is an external service, such as Workflows, calling microservices in a predefined order. </p>
<p>After some restructuring, the following architecture emerged with Workflows:</p>
<p><figure>
  <a href="#img-3e3549aeed71a6e5e1b01e831909af73">
    <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/3_temY387.max-1000x1000.png"
      alt="/img/picadailly-workflows/3_temY387.max-1000x1000.png"
       />
  </a>
  <figcaption>/img/picadailly-workflows/3_temY387.max-1000x1000.png</figcaption>
</figure>
<div class="lightbox" id="img-3e3549aeed71a6e5e1b01e831909af73">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/3_temY387.max-1000x1000.png"
    alt="/img/picadailly-workflows/3_temY387.max-1000x1000.png"
     />
  <div class="lightbox-caption">/img/picadailly-workflows/3_temY387.max-1000x1000.png</div>
</div>
</p>
<p>Let&rsquo;s recap the orchestrated approach:</p>
<ul>
<li>App Engine is still the same web frontend that accepts pictures from our users and stores them in the Cloud Storage bucket. </li>
<li>The file storage events trigger two functions, one for the creation of new pictures and one for the deletion of existing pictures. Both functions create a workflow execution. For file creation, the workflow directly makes the call to the Vision API (declaratively instead of via Cloud Function code) and stores picture metadata in Firestore via its REST API. </li>
<li>In between, there&rsquo;s a function to transform the useful information of the Vision API into a document to be stored in Firestore. Our initial image analysis function has been simplified: The workflow makes the REST API calls and only the data transformation part remains. </li>
<li>If the picture is safe to display, the workflow saves the information in Firestore, otherwise, that&rsquo;s the end of the workflow. </li>
<li>This branch of the workflow ends with calls to Thumbnail and Collage Cloud Run services. This is similar to before, but with no Pub/Sub or Cloud Scheduler to set up. </li>
<li>The other branch of the workflow is for the picture garbage collection. The service itself was completely removed, as it mainly contained API calls without any business logic. Instead, the workflow makes these calls. </li>
</ul>
<p>There is now a central <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/blob/master/workflows/workflows.yaml">workflows.yaml</a> file capturing the business flow. You can also see a visualization of the flow in Cloud Console:</p>
<p><figure>
  <a href="#img-7fac4aae5126c606b82c0f05a21c7ba2">
    <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/4_zpsaVq7.max-1500x1500.png"
      alt="/img/picadailly-workflows/4_zpsaVq7.max-1500x1500.png"
       />
  </a>
  <figcaption>/img/picadailly-workflows/4_zpsaVq7.max-1500x1500.png</figcaption>
</figure>
<div class="lightbox" id="img-7fac4aae5126c606b82c0f05a21c7ba2">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/4_zpsaVq7.max-1500x1500.png"
    alt="/img/picadailly-workflows/4_zpsaVq7.max-1500x1500.png"
     />
  <div class="lightbox-caption">/img/picadailly-workflows/4_zpsaVq7.max-1500x1500.png</div>
</div>
</p>
<p>The Workflows UI shows which executions failed, at which step, so we can see which one had an issue without having to dive through heaps of logs to correlate each service invocation. Workflows also ensures that each service call completes properly, and it can apply global error and retry policies.</p>
<p>With orchestration, the business flows are captured more centrally and explicitly, and can even be version controlled. Each step of a workflow can be monitored, and errors, retries, and timeouts can be laid out clearly in the workflow definition. When using Cloud Workflows in particular, services can be called directly via REST, instead of relying on events on Pub/Sub topics. Furthermore, all the services involved in those processes can remain independent, without knowledge of what other services are doing.</p>
<p>Of course, there are downsides as well. If you add an orchestrator into the picture, you have one more component to worry about, and it could become the single point of failure of your architecture (fortunately, Google Cloud products come with SLAs!). Last, we should mention that relying on REST endpoints might potentially increase coupling, with a heavier reliance on strong payload schemas vs lighter events formats.</p>
<h2 id="lessons-learned">Lessons learned</h2>
<p>Working with Workflows was refreshing in a number of ways and offered us some lessons that are worth sharing. </p>
<h3 id="better-visibility">Better visibility</h3>
<p>It is great to have a high-level overview of the underlying business logic, clearly laid out in the form of a <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/blob/master/workflows/workflows.yaml">YAML declaration</a>. Having visibility into each workflow execution was useful, as it enabled us to clearly understand what worked in each execution, without having to dive into the logs to correlate the various individual service executions.</p>
<h3 id="simpler-code">Simpler code</h3>
<p>In the original event-driven architecture, we had to deal with three types of events:</p>
<ol>
<li>Cloud Functions&rsquo; direct integration with Cloud Storage events</li>
<li>HTTP wrapped Pub/Sub messages with Cloud Storage events for Cloud Run</li>
<li>Eventarc&rsquo;s CloudEvents based Cloud Storage events for Cloud Run</li>
</ol>
<p>As a result, the code had to cater to each flavor of events:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Cloud Functions provides the event directly
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>exports.vision_analysis <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">async</span> (event, context) =&gt; {
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> filename <span style="color:#666">=</span> event.name;
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> filebucket <span style="color:#666">=</span> event.bucket;
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Cloud Run encodes the GCS event in Base64 in a Pub/Sub message
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// and wraps the message in an HTTP request
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>app.post(<span style="color:#4070a0">&#39;/&#39;</span>, <span style="color:#007020;font-weight:bold">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> pubSubMessage <span style="color:#666">=</span> req.body;
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> eventType <span style="color:#666">=</span> pubSubMessage.message.attributes.eventType;
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> fileEvent <span style="color:#666">=</span> JSON.parse(
</span></span><span style="display:flex;"><span> Buffer.from(pubSubMessage.message.data, <span style="color:#4070a0">&#39;base64&#39;</span>)
</span></span><span style="display:flex;"><span> .toString().trim());
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span><span style="">​</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Eventarc encodes events with CloudEvents
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// and Cloud Run wraps it in an HTTP request
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>app.post(<span style="color:#4070a0">&#39;/&#39;</span>, <span style="color:#007020;font-weight:bold">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> cloudEvent <span style="color:#666">=</span> HTTP.toEvent({
</span></span><span style="display:flex;"><span> headers<span style="color:#666">:</span> req.headers, body<span style="color:#666">:</span> req.body });
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> tokens <span style="color:#666">=</span> logEntryData.protoPayload.resourceName.split(<span style="color:#4070a0">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> bucket <span style="color:#666">=</span> tokens[<span style="color:#40a070">3</span>];
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> objectName <span style="color:#666">=</span> tokens[<span style="color:#40a070">5</span>];
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div><p>In the orchestrated version, there&rsquo;s only a simple REST call and HTTP POST body to parse:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// Workflows calls services directly,
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// No events to unwrap
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>app.post(<span style="color:#4070a0">&#39;/&#39;</span>, <span style="color:#007020;font-weight:bold">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span> <span style="color:#60a0b0;font-style:italic">// gs://picture-bucket/image.jpg
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span> <span style="color:#007020;font-weight:bold">const</span> gcsImageUri <span style="color:#666">=</span> req.body.gcsImageUri;
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> tokens <span style="color:#666">=</span> gcsImageUri.substr(<span style="color:#40a070">5</span>).split(<span style="color:#4070a0">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">const</span> fileEvent <span style="color:#666">=</span> { bucket<span style="color:#666">:</span> tokens[<span style="color:#40a070">0</span>], name<span style="color:#666">:</span> tokens[<span style="color:#40a070">1</span>] };
</span></span></code></pre></div><h3 id="less-code">Less code</h3>
<p>Moving REST calls into the workflow definition as a declaration (with straightforward authentication) enabled us to eliminate quite a bit of code in our services; one service was trimmed down into a simple data transformation function, and another service completely disappeared! Two functions for triggering two paths in the workflow were needed though, but with a future integration with Eventarc, they may not be required anymore. </p>
<h3 id="less-setup">Less setup</h3>
<p>In the original event-driven architecture, we had to create Pub/Sub topics, and set up Cloud Scheduler and Eventarc to wire-up services. With Workflows, all of this setup is gone. <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/blob/master/workflows/workflows.yaml">Workflows.yaml</a> is the single source of setup needed for the business flow. </p>
<h3 id="error-handling">Error handling</h3>
<p>Error handling was also simplified in a couple of ways. First, the whole flow stops when an error occurs, so we were no longer in the dark about exactly which services succeeded and which failed in our chain of calls. Second, we now have the option of applying global error and retry policies. </p>
<h3 id="learning-curve">Learning curve</h3>
<p>Now, everything is not always perfect! We had to learn a new service, with its quirks and limited documentation &mdash; it&rsquo;s still early, of course, and the documentation will improve over time with feedback from our customers.</p>
<h3 id="code-vs-yaml">Code vs. YAML </h3>
<p>As we were redesigning the architecture, an interesting question came up over and over: &ldquo;Should we do this in code in a service or should we let Workflows make this call from the YAML definition?&rdquo;</p>
<p>In Workflows, more of the logic lands in the workflow definition file in YAML, rather than code in a service. Code is usually easier to write, test, and debug than YAML, but it also requires more setup and maintenance than a step definition in Workflows.</p>
<p>If it&rsquo;s boilerplate code that simply makes a call to some API, that should be turned into YAML declarations. However, if the code also has extra logic, then it&rsquo;s probably better to leave it in code, as YAML is less testable. Although there is some level of error reporting in the Workflows UI, it&rsquo;s not a full-fledged IDE that helps you along the way. Even when working in your IDE on your development machine, you&rsquo;ll have limited help from the IDE, as it only checks for valid YAML syntax.</p>
<h3 id="loss-of-flexibility">Loss of flexibility</h3>
<p>The last aspect we&rsquo;d like to mention is perhaps a loss of flexibility. Working with a loosely-coupled set of microservices that communicate via events is fairly extensible, compared to a more rigid solution that mandates a strict definition of the business process descriptions.</p>
<h3 id="choreography-or-orchestration">Choreography or orchestration?</h3>
<p>Both approaches are totally valid, and each has its pros and cons. We mentioned this topic when <a href="https://cloud.google.com/blog/topics/developers-practitioners/better-service-orchestration-workflows">introducing Workflows</a>. When should you choose one approach over the other? Choreography can be a better fit if services are not closely related, or if they can exist in different bounded contexts. Whereas orchestration might be best if you can describe the business logic of your application as a clear flow chart, which can then directly be described in a workflow definition. </p>
<h2 id="next-steps">Next steps</h2>
<p>To go further, we invite you to have a closer look at <a href="http://cloud.google.com/workflows">Workflows</a>, and its supported <a href="https://cloud.google.com/workflows#all-features">features</a>, by looking at the <a href="https://cloud.google.com/workflows/docs/overview">documentation</a>, particularly the <a href="https://cloud.google.com/workflows/docs/reference/syntax">reference documentation</a> and the <a href="https://cloud.google.com/workflows/docs/sample-workflows?hl=en">examples</a>. We also have a series of short articles that cover Workflows, with various <a href="https://glaforge.appspot.com/category/Google%20Cloud%20Platform">tips and tricks</a>, as well as introductions to Workflows, with a <a href="https://atamel.dev/posts/2020/09-08_first_look_at_workflows/">first look at Workflows</a> and some thoughts on <a href="https://glaforge.appspot.com/article/orchestrating-microservices-with-cloud-workflows">choreography vs orchestration</a>.</p>
<p>If you want to study a concrete use case, with an event-based architecture and an equivalent orchestrated approach, feel free to look into our <a href="https://g.co/codelabs/serverless-workshop">Serverless Workshop</a>. It offers codelabs spanning Cloud Functions, Cloud Run, App Engine, Eventarc, and Workflows. In particular, lab 6 is the one in which we converted the event-based model into an orchestration with Workflows. All the code is also available as <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop">open source on GitHub</a>.<br />
We look forward to hearing from you about your workflow experiments and needs. Feel free to reach out to us on Twitter at <a href="https://twitter.com/glaforge">@glaforge</a> and <a href="https://twitter.com/meteatamel">@meteatamel</a>.</p>
<p><a href="https://cloud.google.com/blog/products/application-development/get-to-know-google-cloud-workflows"></a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 15 with Workflows — Built-in Cloud Logging function</title><link>https://glaforge.dev/posts/2021/02/10/day-15-with-workflows-built-in-cloud-logging-function/</link><pubDate>Wed, 10 Feb 2021 16:01:24 +0100</pubDate><guid>https://glaforge.dev/posts/2021/02/10/day-15-with-workflows-built-in-cloud-logging-function/</guid><description>&lt;p>In the two previous episodes, we saw how to
&lt;a href="https://glaforge.dev/posts/2021/02/03/day-14-with-workflows-subworkflows/">create and call subworkflows&lt;/a>,
and we applied this technique to making a reusable routine for logging with Cloud Logging.
However, there&amp;rsquo;s already a built-in function for that purpose! So let&amp;rsquo;s have a look at this integration.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/V2hQekDwdRM?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>To call the built-in logging function, just create a new step, and make a call to the &lt;code>sys.log&lt;/code> function:&lt;/p></description><content:encoded>
<![CDATA[<p>In the two previous episodes, we saw how to
<a href="https://glaforge.dev/posts/2021/02/03/day-14-with-workflows-subworkflows/">create and call subworkflows</a>,
and we applied this technique to making a reusable routine for logging with Cloud Logging.
However, there&rsquo;s already a built-in function for that purpose! So let&rsquo;s have a look at this integration.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/V2hQekDwdRM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>To call the built-in logging function, just create a new step, and make a call to the <code>sys.log</code> function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">logString</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb">  </span>Hello  Cloud  Logging!<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">severity</span>:<span style="color:#bbb">  </span>INFO<span style="color:#bbb">
</span></span></span></code></pre></div><p>This function takes a mandatory parameter: text. And an optional one: severity.</p>
<p>The text parameter accepts all types of supported values, so it&rsquo;s not only string,
but all kinds of numbers, as well as arrays and dictionaries.
Their string representation will be used as text.</p>
<p>The optional severity parameter is an enum that can take the values:
<code>DEFAULT</code>, <code>DEBUG</code>, <code>INFO</code>, <code>NOTICE</code>, <code>WARNING</code>, <code>ERROR</code>, <code>CRITICAL</code>, <code>ALERT</code>, <code>EMERGENCY</code>,
with <code>DEFAULT</code> being&hellip; the default value if you don&rsquo;t specify a severity!</p>
<p>Here&rsquo;s another example with a dictionary as parameter, which will be output as text in the logs, and a severity of <code>WARNING</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">createDict</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">person</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb">  </span>Guillaume<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">kids</span>:<span style="color:#bbb">  </span><span style="color:#40a070">2</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">logDict</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>sys.log<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">text</span>:<span style="color:#bbb">  </span>${person}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">severity</span>:<span style="color:#bbb">  </span>WARNING<span style="color:#bbb">
</span></span></span></code></pre></div><p>Looking at the results in the cloud logging console, you will see both messages appear:</p>
<p><figure>
  <a href="#img-e6ccc852f25079deabb42826dbefd891">
    <img src="/img/workflows-days/w15-builtin-log.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e6ccc852f25079deabb42826dbefd891">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/w15-builtin-log.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Don&rsquo;t hesitate to have a look at <a href="https://cloud.google.com/workflows/docs/reference/stdlib/sys/log">reference documentation</a>
to find more about the available built-in functions.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 14 with Workflows — Subworkflows</title><link>https://glaforge.dev/posts/2021/02/03/day-14-with-workflows-subworkflows/</link><pubDate>Wed, 03 Feb 2021 17:02:24 +0100</pubDate><guid>https://glaforge.dev/posts/2021/02/03/day-14-with-workflows-subworkflows/</guid><description>&lt;p>Workflows are made of sequences of steps and branches.
Sometimes, some particular sequence of steps can be repeated, and it would be a good idea to avoid error-prone repetitions in your workflow definition
(in particular if you change in one place, and forget to change in another place).
You can modularize your definition by creating subworkflows, a bit like subroutines or functions in programming languages.
For example, yesterday, we had a look at &lt;a href="https://glaforge.dev/posts/2021/02/02/day-13-with-workflows-logging-with-cloud-logging/">how to log to Cloud Logging&lt;/a>:
if you want to log in several places in your workflow, you can extract that routine in a subworkflow.&lt;/p></description><content:encoded>
<![CDATA[<p>Workflows are made of sequences of steps and branches.
Sometimes, some particular sequence of steps can be repeated, and it would be a good idea to avoid error-prone repetitions in your workflow definition
(in particular if you change in one place, and forget to change in another place).
You can modularize your definition by creating subworkflows, a bit like subroutines or functions in programming languages.
For example, yesterday, we had a look at <a href="https://glaforge.dev/posts/2021/02/02/day-13-with-workflows-logging-with-cloud-logging/">how to log to Cloud Logging</a>:
if you want to log in several places in your workflow, you can extract that routine in a subworkflow.</p>
<p>Let&rsquo;s see that in action in the video below, and you can read all the explanations afterwards:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/tbiFaO_LOdg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>First things first, let&rsquo;s step back and look at the structure of workflow definitions.
You write a series of steps, directly in the main YAML file.
You can move back and forth between steps thanks to
<a href="https://glaforge.dev/posts/2020/12/04/day-4-with-workflows-jumping-with-switch-conditions/">jumps</a>,
but it wouldn&rsquo;t be convenient to use jumps to emulate subroutines
(remember the good old days of BASIC and its gotos?).
Instead, Cloud Workflows allows you to separate steps under a <code>main</code>, and subroutines under their own subroutine name.</p>
<p>So far we had just a sequence of steps:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">stepOne</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">stepTwo</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">stepThree</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span></code></pre></div><p>Those steps are implicitly under a <code>main</code> routine.
And here&rsquo;s how to show this main routine explicitly, by having that <code>main</code> block, and <code>steps</code> underneath:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">stepOne</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">stepTwo</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">stepThree</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span></code></pre></div><p>To create a subworkflow, we follow the same structure, but with a different name than <code>main</code>, but you can also pass parameters like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">subWorkflow</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params:  [param1,  param2,  param3</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;default value&#34;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">stepOne</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">stepTwo</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">stepThree</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>...<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice that you can pass several parameters, and that parameters can have default values when that parameter is not provided at the call site.</p>
<p>Then in your main flow, you can call that subworkflow with a <code>call</code> instruction.
Let&rsquo;s take a look at a concrete example, that simply concatenates two strings:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">greet</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>greet<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">greeting</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Hello&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Guillaume&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>concatenation<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">returning</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${concatenation}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">greet</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params:  [greeting,  name</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;World&#34;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">append</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${greeting  +  &#34;, &#34;  +  name  +  &#34;!&#34;}<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the <code>call</code> instruction, we pass the <code>greeting</code> and <code>name</code> arguments, and the result will contain the output of the subworkflow call.
In the subworkflow, we defined our parameters, and we have a single step just return an expression which is the desired greeting message concatenation.</p>
<p>One last example, but perhaps more useful than concatenating strings!
Let&rsquo;s turn yesterday&rsquo;s Cloud Logging integration into a reusable subworkflow.
That way, you&rsquo;ll be able to call the log subworkflow as many times as needed in your main workflow definition, without repeating yourself:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">first_log_msg</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>logMessage<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">msg</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;First message&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">second_log_msg</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>logMessage<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">msg</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Second message&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">logMessage</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[msg]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#062873;font-weight:bold">log</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://logging.googleapis.com/v2/entries:write<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#062873;font-weight:bold">entries</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>- <span style="color:#062873;font-weight:bold">logName</span>:<span style="color:#bbb">  </span>${&#34;projects/&#34;  +  sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;)  +  &#34;/logs/workflow_logger&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                      </span><span style="color:#062873;font-weight:bold">resource</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;audited_resource&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                        </span><span style="color:#062873;font-weight:bold">labels</span>:<span style="color:#bbb">  </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                      </span><span style="color:#062873;font-weight:bold">textPayload</span>:<span style="color:#bbb">  </span>${msg}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And voila! We called our <code>logMessage</code> subworkflow twice in our main workflow, just passing the text message to log into Cloud Logging.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 13 with Workflows — Logging with Cloud Logging</title><link>https://glaforge.dev/posts/2021/02/02/day-13-with-workflows-logging-with-cloud-logging/</link><pubDate>Tue, 02 Feb 2021 17:35:19 +0100</pubDate><guid>https://glaforge.dev/posts/2021/02/02/day-13-with-workflows-logging-with-cloud-logging/</guid><description>&lt;p>Time to come back to our series on Cloud Workflows.
Sometimes, for debugging purposes or for auditing, it is useful to be able to log some information via Cloud Logging.
As we saw last month, you can
&lt;a href="https://glaforge.dev/posts/2020/12/15/day-8-with-workflows-calling-an-http-endpoint/">call HTTP endpoints&lt;/a> from your workflow.
We can actually use
&lt;a href="https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write">Cloud Logging&amp;rsquo;s REST API&lt;/a> to log such messages!
Let&amp;rsquo;s see that in action.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#062873;font-weight:bold">log&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">call&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>http.post&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">args&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>&lt;span style="color:#062873;font-weight:bold">url&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>https://logging.googleapis.com/v2/entries:write&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>&lt;span style="color:#062873;font-weight:bold">auth&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">            &lt;/span>&lt;span style="color:#062873;font-weight:bold">type&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>OAuth2&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>&lt;span style="color:#062873;font-weight:bold">body&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">            &lt;/span>&lt;span style="color:#062873;font-weight:bold">entries&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">                &lt;/span>- &lt;span style="color:#062873;font-weight:bold">logName&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>${&amp;#34;projects/&amp;#34; + sys.get_env(&amp;#34;GOOGLE_CLOUD_PROJECT_ID&amp;#34;) + &amp;#34;/logs/workflow_logger&amp;#34;}&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">                  &lt;/span>&lt;span style="color:#062873;font-weight:bold">resource&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">                    &lt;/span>&lt;span style="color:#062873;font-weight:bold">type&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#4070a0">&amp;#34;audited_resource&amp;#34;&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">                    &lt;/span>&lt;span style="color:#062873;font-weight:bold">labels&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>{}&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">                  &lt;/span>&lt;span style="color:#062873;font-weight:bold">textPayload&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Hello World from Cloud Workflows!&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We call the &lt;a href="https://logging.googleapis.com/v2/entries:write">https://logging.googleapis.com/v2/entries:write&lt;/a> API endpoint to write new logging entries.
We authenticate via OAuth2&amp;mdash;as long as the service account used for the workflow execution allows it to use the logging API.
Then we pass a JSON structure as the body of the call, indicating the name of the logger to use,
which resources it applies to, and also the textPayload containing our text message. You could also use a ${} expression to log more complex values.&lt;/p></description><content:encoded>
<![CDATA[<p>Time to come back to our series on Cloud Workflows.
Sometimes, for debugging purposes or for auditing, it is useful to be able to log some information via Cloud Logging.
As we saw last month, you can
<a href="https://glaforge.dev/posts/2020/12/15/day-8-with-workflows-calling-an-http-endpoint/">call HTTP endpoints</a> from your workflow.
We can actually use
<a href="https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write">Cloud Logging&rsquo;s REST API</a> to log such messages!
Let&rsquo;s see that in action.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">log</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.post<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://logging.googleapis.com/v2/entries:write<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span>OAuth2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">entries</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>- <span style="color:#062873;font-weight:bold">logName</span>:<span style="color:#bbb">  </span>${&#34;projects/&#34;  +  sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;)  +  &#34;/logs/workflow_logger&#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">resource</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;audited_resource&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span><span style="color:#062873;font-weight:bold">labels</span>:<span style="color:#bbb">  </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                  </span><span style="color:#062873;font-weight:bold">textPayload</span>:<span style="color:#bbb">  </span>Hello  World  from  Cloud  Workflows!<span style="color:#bbb">
</span></span></span></code></pre></div><p>We call the <a href="https://logging.googleapis.com/v2/entries:write">https://logging.googleapis.com/v2/entries:write</a> API endpoint to write new logging entries.
We authenticate via OAuth2&mdash;as long as the service account used for the workflow execution allows it to use the logging API.
Then we pass a JSON structure as the body of the call, indicating the name of the logger to use,
which resources it applies to, and also the textPayload containing our text message. You could also use a ${} expression to log more complex values.</p>
<p>Once this workflow definition is done and deployed, you can execute it, and you should see in the logs your message appear:</p>
<p><figure>
  <a href="#img-e02cfbf04d7403d351a529cb4bba4556">
    <img src="/img/workflows-days/w13-cloud-logging.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e02cfbf04d7403d351a529cb4bba4556">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/w13-cloud-logging.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Voila! You can log messages to Cloud Logging!</p>
<p>Let&rsquo;s recap in this video:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/XwzSgBB6Kq4?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>In the next episode, we&rsquo;ll take advantage of subworkflows,
to create a reusable set of steps that you will be able to call several times throughout your workflow definition,
without repeating yourself, by turning this logging example into a subworkflow.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 12 with Workflows — Loops and iterations</title><link>https://glaforge.dev/posts/2020/12/23/day-12-with-workflows-loops-and-iterations/</link><pubDate>Wed, 23 Dec 2020 19:00:18 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/23/day-12-with-workflows-loops-and-iterations/</guid><description>&lt;p>In previous episodes of this Cloud Workflows series, we&amp;rsquo;ve learned about
&lt;a href="https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/">variable assignment&lt;/a>,
data structures like &lt;a href="https://glaforge.dev/posts/2020/12/09/day-6-with-workflows-arrays-and-dictionaries/">arrays&lt;/a>,
&lt;a href="https://glaforge.dev/posts/2020/12/02/day-2-with-workflows-a-workflow-is-made-of-steps-and-jumps/">jumps&lt;/a> and
&lt;a href="https://glaforge.dev/posts/2020/12/04/day-4-with-workflows-jumping-with-switch-conditions/">switch conditions&lt;/a> to move between steps,
and &lt;a href="https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/">expressions&lt;/a>
to do some computations, including potentially some built-in functions.&lt;/p>
&lt;p>With all these previous learnings, we are now equipped with all the tools to let us create loops and iterations,
like for example, iterating over the element of an array, perhaps to call an API several times but with different arguments.
So let&amp;rsquo;s see how to create such an iteration!&lt;/p></description><content:encoded>
<![CDATA[<p>In previous episodes of this Cloud Workflows series, we&rsquo;ve learned about
<a href="https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/">variable assignment</a>,
data structures like <a href="https://glaforge.dev/posts/2020/12/09/day-6-with-workflows-arrays-and-dictionaries/">arrays</a>,
<a href="https://glaforge.dev/posts/2020/12/02/day-2-with-workflows-a-workflow-is-made-of-steps-and-jumps/">jumps</a> and
<a href="https://glaforge.dev/posts/2020/12/04/day-4-with-workflows-jumping-with-switch-conditions/">switch conditions</a> to move between steps,
and <a href="https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/">expressions</a>
to do some computations, including potentially some built-in functions.</p>
<p>With all these previous learnings, we are now equipped with all the tools to let us create loops and iterations,
like for example, iterating over the element of an array, perhaps to call an API several times but with different arguments.
So let&rsquo;s see how to create such an iteration!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/OXhV2cuKwo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>First of all, let&rsquo;s prepare some variable assignments:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">define</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">array</span>:<span style="color:#bbb">  </span>[<span style="color:#4070a0">&#39;Google&#39;</span>,<span style="color:#bbb">  </span><span style="color:#4070a0">&#39;Cloud&#39;</span>,<span style="color:#bbb">  </span><span style="color:#4070a0">&#39;Workflows&#39;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">i</span>:<span style="color:#bbb">  </span><span style="color:#40a070">0</span><span style="color:#bbb">
</span></span></span></code></pre></div><ul>
<li>The <code>array</code> variable will hold the values we&rsquo;ll be iterating over.</li>
<li>The <code>result</code> variable contains a string to which we&rsquo;ll append each values from the array.</li>
<li>And the <code>i</code> variable is an index, to know our position in the array.</li>
</ul>
<p>Next, like in a for loop of programming languages, we need to prepare a condition for the loop to finish. We&rsquo;ll do that in a dedicated step:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">checkCondition</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb">  </span>${i  &lt;  len(array)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>iterate<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>returnResult<span style="color:#bbb">
</span></span></span></code></pre></div><p>We define a <code>switch</code>, with a <code>condition</code> expression that compares the current index position with the length of the array, using the built-in <code>len()</code> function.
If the condition is true, we&rsquo;ll go to an <code>iterate</code> step. If it&rsquo;s false, we&rsquo;ll go to the ending step (called <code>returnResult</code> here).</p>
<p>Let&rsquo;s tackle the iteration body itself.
Here, it&rsquo;s quite simple, as we&rsquo;re just assigning new values to the variables:
we append the i-th element of the array into the result variable, and we increment the index by one.
Then we go back to the <code>checkCondition</code> step.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">iterate</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>${result  +  array[i]  +  &#34; &#34;}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">i</span>:<span style="color:#bbb">  </span>${i+1}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>checkCondition<span style="color:#bbb">
</span></span></span></code></pre></div><p>Note that if we were doing something more convoluted, for example calling an HTTP endpoint with the element of the array as argument,
we would need two steps: one for the actual HTTP endpoint call, and one for incrementing the index value.
However in the example above, we&rsquo;re only assigning variables, so we did the whole body of the iteration in this simple assignment step.</p>
<p>When going through the <code>checkCondition</code> step, if the condition is not met (ie. we&rsquo;ve reached the end of the array), then we&rsquo;re redirected to the <code>returnResult</code> step:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">returnResult</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${result}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This final step simply returns the value of the <code>result</code> variable.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 11 with Workflows — Sleeping in a workflow</title><link>https://glaforge.dev/posts/2020/12/22/day-11-with-workflows-sleeping-in-a-workflow/</link><pubDate>Tue, 22 Dec 2020 19:05:26 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/22/day-11-with-workflows-sleeping-in-a-workflow/</guid><description>&lt;p>Workflows are not necessarily instantaneous, and executions can span over a long period of time.
Some steps may potentially launch asynchronous operations, which might take seconds or minutes to finish, but you are not notified when the process is over.
So when you want for something to finish, for example before polling again to check the status of the async operation, you can introduce a sleep operation in your workflows.&lt;/p></description><content:encoded>
<![CDATA[<p>Workflows are not necessarily instantaneous, and executions can span over a long period of time.
Some steps may potentially launch asynchronous operations, which might take seconds or minutes to finish, but you are not notified when the process is over.
So when you want for something to finish, for example before polling again to check the status of the async operation, you can introduce a sleep operation in your workflows.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/uaW_Cv3RCxQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>To introduce a <a href="https://cloud.google.com/workflows/docs/reference/syntax">sleep operation</a>, add a step in the workflow with a call to the built-in sleep operation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">someSleep</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>sys.sleep<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">seconds</span>:<span style="color:#bbb">  </span><span style="color:#40a070">10</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">returnOutput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>We  waited  for  10  seconds!<span style="color:#bbb">
</span></span></span></code></pre></div><p>A <code>sys.sleep</code> operation takes a <code>seconds</code> argument, where you can specify the number of seconds to wait.</p>
<p>By combining conditional jumps and sleep operations, you can easily implement polling some resource or API at a regular interval, to double check that it completed.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 10 with Workflows — Accessing built-in environment variables</title><link>https://glaforge.dev/posts/2020/12/17/day-10-with-workflows-accessing-built-in-environment-variables/</link><pubDate>Thu, 17 Dec 2020 19:13:07 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/17/day-10-with-workflows-accessing-built-in-environment-variables/</guid><description>&lt;p>&lt;a href="https://cloud.google.com/workflows">Google Cloud Workflows&lt;/a> offers a few built-in environment variables that are accessible from your workflow executions.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/sJQWbo49pWg?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>There are currently &lt;a href="https://cloud.google.com/workflows/docs/reference/environment-variables">5 environment variables&lt;/a> that are defined:&lt;/p>
&lt;ul>
&lt;li>&lt;code>GOOGLE_CLOUD_PROJECT_NUMBER&lt;/code>: The workflow project&amp;rsquo;s number.&lt;/li>
&lt;li>&lt;code>GOOGLE_CLOUD_PROJECT_ID&lt;/code>: The workflow project&amp;rsquo;s identifier.&lt;/li>
&lt;li>&lt;code>GOOGLE_CLOUD_LOCATION&lt;/code>: The workflow&amp;rsquo;s location.&lt;/li>
&lt;li>&lt;code>GOOGLE_CLOUD_WORKFLOW_ID&lt;/code>: The workflow&amp;rsquo;s identifier.&lt;/li>
&lt;li>&lt;code>GOOGLE_CLOUD_WORKFLOW_REVISION_ID&lt;/code>: The workflow&amp;rsquo;s revision identifier.&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s see how to access them from our workflow definition:&lt;/p></description><content:encoded>
<![CDATA[<p><a href="https://cloud.google.com/workflows">Google Cloud Workflows</a> offers a few built-in environment variables that are accessible from your workflow executions.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/sJQWbo49pWg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>There are currently <a href="https://cloud.google.com/workflows/docs/reference/environment-variables">5 environment variables</a> that are defined:</p>
<ul>
<li><code>GOOGLE_CLOUD_PROJECT_NUMBER</code>: The workflow project&rsquo;s number.</li>
<li><code>GOOGLE_CLOUD_PROJECT_ID</code>: The workflow project&rsquo;s identifier.</li>
<li><code>GOOGLE_CLOUD_LOCATION</code>: The workflow&rsquo;s location.</li>
<li><code>GOOGLE_CLOUD_WORKFLOW_ID</code>: The workflow&rsquo;s identifier.</li>
<li><code>GOOGLE_CLOUD_WORKFLOW_REVISION_ID</code>: The workflow&rsquo;s revision identifier.</li>
</ul>
<p>Let&rsquo;s see how to access them from our workflow definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">envVars</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">projectID</span>:<span style="color:#bbb">  </span>${sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_ID&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">projectNum</span>:<span style="color:#bbb">  </span>${sys.get_env(&#34;GOOGLE_CLOUD_PROJECT_NUMBER&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">projectLocation</span>:<span style="color:#bbb">  </span>${sys.get_env(&#34;GOOGLE_CLOUD_LOCATION&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">workflowID</span>:<span style="color:#bbb">  </span>${sys.get_env(&#34;GOOGLE_CLOUD_WORKFLOW_ID&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">workflowRev</span>:<span style="color:#bbb">  </span>${sys.get_env(&#34;GOOGLE_CLOUD_WORKFLOW_REVISION_ID&#34;)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">output</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${projectID  +  &#34; &#34;  +  projectNum  +  &#34; &#34;  +  projectLocation  +  &#34; &#34;  +  workflowID  +  &#34; &#34;  +  workflowRev}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We use the built-in <code>sys.get_env()</code> function to access those variables.
We&rsquo;ll revisit the various existing built-in functions in later episodes.</p>
<p>Then when you execute this workflow, you&rsquo;ll get an output like this:</p>
<pre tabindex="0"><code>&#34;workflows-days 783331365595 europe-west4 w10-builtin-env-vars 000001-3af&#34;
</code></pre><p>There&rsquo;s one variable I&rsquo;d like to see added to this list, that would be the current execution ID.
That could potentially be useful for identifying a particular execution, when looking in the logs, to reason about potential failure, or for auditing purposes.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 9 with Workflows — Deploying and executing Workflows from the command-line</title><link>https://glaforge.dev/posts/2020/12/16/day-9-with-workflows-deploying-and-executing-workflows-from-the-command-line/</link><pubDate>Wed, 16 Dec 2020 19:44:08 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/16/day-9-with-workflows-deploying-and-executing-workflows-from-the-command-line/</guid><description>&lt;p>So far, in this series on &lt;a href="https://cloud.google.com/workflows">Cloud Workflows&lt;/a>,
we&amp;rsquo;ve only used the Google Cloud Console UI to manage our workflow definitions, and their executions.
But it&amp;rsquo;s also possible to deploy new definitions and update existing ones from the command-line,
using the &lt;a href="https://cloud.google.com/sdk/">GCloud SDK&lt;/a>. Let&amp;rsquo;s see how to do that!&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/rVTPRUuidPI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>If you don&amp;rsquo;t already have an existing service account, you should create one following these
&lt;a href="https://cloud.google.com/workflows/docs/creating-updating-workflow#gcloud">instructions&lt;/a>.
I&amp;rsquo;m going to use the workflow-sa service account I created for the purpose of this demonstration.&lt;/p></description><content:encoded>
<![CDATA[<p>So far, in this series on <a href="https://cloud.google.com/workflows">Cloud Workflows</a>,
we&rsquo;ve only used the Google Cloud Console UI to manage our workflow definitions, and their executions.
But it&rsquo;s also possible to deploy new definitions and update existing ones from the command-line,
using the <a href="https://cloud.google.com/sdk/">GCloud SDK</a>. Let&rsquo;s see how to do that!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/rVTPRUuidPI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>If you don&rsquo;t already have an existing service account, you should create one following these
<a href="https://cloud.google.com/workflows/docs/creating-updating-workflow#gcloud">instructions</a>.
I&rsquo;m going to use the workflow-sa service account I created for the purpose of this demonstration.</p>
<p>Our workflow definition is a simple &ldquo;hello world&rdquo; like the one we created for
<a href="https://glaforge.dev/posts/2020/12/01/day-1-with-workflows-your-first-step-to-hello-world/">day #1</a>
of our exploration of Google Cloud Workflows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">hello</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>Hello  from  gcloud!<span style="color:#bbb">
</span></span></span></code></pre></div><p>To deploy this workflow definition, we&rsquo;ll launch the following gcloud command,
specifying the name of our workflow, passing the local source definition, and the service account:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud beta workflows deploy w09-new-workflow-from-cli <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --source<span style="color:#666">=</span>w09-hello-from-gcloud.yaml <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --service-account<span style="color:#666">=</span>workflow-sa@workflows-days.iam.gserviceaccount.com
</span></span></code></pre></div><p>You can also add labels with the <code>--labels</code> flag, and a description with the <code>--description</code> flag, just like in the Google Cloud Console UI.</p>
<p>If you want to update the workflow definition, this is also the same command to invoke, passing the new version of your definition file.</p>
<p>Time to create an execution of our workflow!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud beta workflows run w09-new-workflow-from-cli
</span></span></code></pre></div><p>You will see an output similar to this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>Waiting for execution [d4a3f4d4-db45-48dc-9c02-d25a05b0e0ed] to complete...done.<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">argument</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;null&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">endTime</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;2020-12-16T11:32:25.663937037Z&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>projects/783331365595/locations/us-central1/workflows/w09-new-workflow-from-cli/executions/d4a3f4d4-db45-48dc-9c02-d25a05b0e0ed<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;&#34;Hello from gcloud!&#34;&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">startTime</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;2020-12-16T11:32:25.526194298Z&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">state</span>:<span style="color:#bbb"> </span>SUCCEEDED<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">workflowRevisionId</span>:<span style="color:#bbb"> </span><span style="color:#40a070">000001</span>-47f<span style="color:#bbb">
</span></span></span></code></pre></div><p>Our workflow being very simple, it executed and completed right away, hence why you see the result string
(our Hello from gcloud! message), as well as the state as SUCCEEDED.
However, workflows often take longer to execute, consisting of many steps.
If the workflow hasn&rsquo;t yet completed, you&rsquo;ll see its status as <code>ACTIVE</code> instead, or potentially <code>FAILED</code> if something went wrong.</p>
<p>When the workflow takes a long time to complete, you can check the status of the last execution from your shell session with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud beta workflows executions describe-last
</span></span></code></pre></div><p>If you want to know about the ongoing workflow executions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud beta workflows executions list your-workflow-name
</span></span></code></pre></div><p>It&rsquo;ll give you a list of operation IDs for those ongoing executions. You can then inspect a particular one with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud beta workflows executions describe the-operation-id
</span></span></code></pre></div><p>There are other operations on executions, to wait for an execution to finish, or even cancel an ongoing, unfinished execution.</p>
<p>You can learn more about workflow execution in the <a href="https://cloud.google.com/workflows/docs/executing-workflow">documentation</a>.
And in some upcoming episodes, we&rsquo;ll also have a look at how to create workflow executions from client libraries, and from the Cloud Workflows REST API.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 8 with Workflows — Calling an HTTP endpoint</title><link>https://glaforge.dev/posts/2020/12/15/day-8-with-workflows-calling-an-http-endpoint/</link><pubDate>Tue, 15 Dec 2020 19:51:12 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/15/day-8-with-workflows-calling-an-http-endpoint/</guid><description>&lt;p>Time to do something pretty handy: calling an HTTP endpoint, from your Google Cloud Workflows definitions.
Whether calling GCP specific APIs such as the ML APIs, REST APIs of other products like Cloud Firestore,
or when calling your own services, third-party, external APIs, this capability lets you plug your business processes to the external world!&lt;/p>
&lt;p>Let&amp;rsquo;s see calling HTTP endpoints in action in the following video, before diving into the details below:&lt;/p></description><content:encoded>
<![CDATA[<p>Time to do something pretty handy: calling an HTTP endpoint, from your Google Cloud Workflows definitions.
Whether calling GCP specific APIs such as the ML APIs, REST APIs of other products like Cloud Firestore,
or when calling your own services, third-party, external APIs, this capability lets you plug your business processes to the external world!</p>
<p>Let&rsquo;s see calling HTTP endpoints in action in the following video, before diving into the details below:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/jyIonG-u4eM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>By default, when creating a new workflow definition, a default snippet / example is provided for your inspiration.
We&rsquo;ll take a look at it for this article.
There are actually two HTTP endpoint calls, the latter depending on the former:
the first step (<code>getCurrentTime</code>) is a cloud function returning the day of the week,
whereas the second step (<code>readWikipedia</code>) searches Wikipedia for articles about that day of the week.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">getCurrentTime</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://us-central1-workflowsample.cloudfunctions.net/datetime<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>CurrentDateTime<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>getCurrentTime</code> step contains a call attribute of type <code>http.get</code>, to make HTTP GET requests to an API endpoint.
You have the ability to do either <code>call: http.get</code> or <code>call: http.post</code>.
For other methods, you&rsquo;ll have to do call: <code>http.request</code>, and add another key/value pair under <code>args</code>, with method:
<code>GET</code>, <code>POST</code>, <code>PATCH</code> or <code>DELETE</code>.
Under <code>args</code>, for now, we&rsquo;ll just put the URL of our HTTP endpoint.
The last key will be the result, which gives the name of a new variable that will contain the response of our HTTP request.</p>
<p>Let&rsquo;s call Wikipedia with our day of the week search query:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">readWikipedia</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>https://en.wikipedia.org/w/api.php<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">action</span>:<span style="color:#bbb">  </span>opensearch<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">search</span>:<span style="color:#bbb">  </span>${CurrentDateTime.body.dayOfTheWeek}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb">  </span>WikiResult<span style="color:#bbb">
</span></span></span></code></pre></div><p>Same thing with <code>call</code>, and <code>args.url</code>, however, we have a query where you can define the query parameters for the Wikipedia API.
Also note how we can pass data from the previous step function invocation: <code>CurrentDateTime.body.dayOfTheWeek</code>.
We retrieve the body of the response of the previous call, and from there, we get the <code>dayOfTheWeek</code> key in the resulting JSON document.
We then return <code>WikiResult</code>, which is the response of that new API endpoint call.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">returnOutput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${WikiResult.body[1]}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, the last step is here to return the result of our search. We retrieve the body of the response.
The response&rsquo;s body is an array, with a first term being the search query,
and the second item is the following array of document names, which is what our workflow execution will return:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday Night Football&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday Night Wars&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday Night Countdown&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday Morning (newsletter)&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday Night Golf&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday Mornings&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday (The X-Files)&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday&#39;s Child&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#4070a0">&#34;Monday.com&#34;</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>So our whole workflow was able to orchestrate two independent API endpoints, one after the other.
Instead of having two APIs that are coupled via some messaging passing mechanism,
or worse, via explicit calls to one or the other, Cloud Workflows is here to organize those two calls.
It&rsquo;s the orchestration approach, instead of a choreography of services
(see my previous article on <a href="https://glaforge.dev/posts/2020/11/18/orchestrating-microservices-with-cloud-workflows/">orchestration vs choreography</a>,
and my colleague&rsquo;s article on <a href="https://cloud.google.com/blog/topics/developers-practitioners/better-service-orchestration-workflows">better service orchestration</a> with Cloud Workflows).</p>
<p>To come back to the details of API endpoint calls, here&rsquo;s their structure:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">STEP_NAME</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb">  </span>{http.get|http.post|http.request}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb">  </span>URL_VALUE<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[method</span>:<span style="color:#bbb">  </span>REQUEST_METHOD]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[headers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>KEY:VALUE  ...]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[body</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>KEY:VALUE  ...]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>KEY:VALUE  ...]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[auth</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>type:{OIDC|OAuth2}]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[timeout</span>:<span style="color:#bbb">  </span>VALUE_IN_SECONDS]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">[result</span>:<span style="color:#bbb">  </span>RESPONSE_VALUE]<span style="color:#bbb">
</span></span></span></code></pre></div><p>In addition to the URL, the method and query, note that you can pass headers and a body. There is also a built-in mechanism for authentication which works with GCP APIs: the authentication is done transparently. You can also specify a timeout in seconds, if you want to fail fast and not wait forever a response that never comes. But we&rsquo;ll come back to error handling in some of our upcoming articles.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 7 with Workflows — Pass an input argument to your workflow</title><link>https://glaforge.dev/posts/2020/12/10/day-7-with-workflows-pass-an-input-argument-to-your-workflow/</link><pubDate>Thu, 10 Dec 2020 20:04:09 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/10/day-7-with-workflows-pass-an-input-argument-to-your-workflow/</guid><description>&lt;p>All the workflow definitions we&amp;rsquo;ve seen so far, in this series, were self-contained.
They were not parameterized. But we often need our business processes to take arguments
(the ID of an order, the details of the order, etc.), so that we can treat those input values and do something about them.
That&amp;rsquo;s where workflow input parameters become useful!&lt;/p>
&lt;p>Let&amp;rsquo;s start with a simple greeting message that we want to customize with a &lt;code>firstname&lt;/code> and &lt;code>lastname&lt;/code>.
We&amp;rsquo;d like our workflow to look something like this:&lt;/p></description><content:encoded>
<![CDATA[<p>All the workflow definitions we&rsquo;ve seen so far, in this series, were self-contained.
They were not parameterized. But we often need our business processes to take arguments
(the ID of an order, the details of the order, etc.), so that we can treat those input values and do something about them.
That&rsquo;s where workflow input parameters become useful!</p>
<p>Let&rsquo;s start with a simple greeting message that we want to customize with a <code>firstname</code> and <code>lastname</code>.
We&rsquo;d like our workflow to look something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">output</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${&#34;Your  name  is  &#34; + person.firstname + &#34;  &#34; + person.lastname}<span style="color:#bbb">
</span></span></span></code></pre></div><p>In the example above, we have a <code>person</code> variable, on which we&rsquo;re requesting the fields <code>firstname</code> and <code>lastname</code>.
This is actually a dictionary. But how do we let Cloud Workflows know about this variable? We need to define it somehow.</p>
<p>Workflow arguments are global to all the steps, so they need to be defined outside the scope of the steps themselves.
Actually, workflows can be structured in sub-workflows:
there&rsquo;s a main workflow, and possibly additional sub-workflows which are like routines or internal function definitions.
We&rsquo;ll revisit the topic of sub-workflows in a later article.
To declare our input parameter, we&rsquo;ll do it at the level of the main workflow, but in a more explicit fashion, with the following notation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb">  </span>[person]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">output</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${&#34;Your  name  is  &#34; + person.firstname + &#34;  &#34; + person.lastname}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We explicitly show the name of our main workflow. We use the <code>params</code> instruction.
Note that our single argument, <code>person</code>, is surrounded by square brackets.
The main workflow can only take a single dictionary parameter, however, as we&rsquo;ll see later,
sub-workflows can take several input arguments, hence the square brackets notation to specify a list of arguments.</p>
<p><figure>
  <a href="#img-f2940eb12fa1533e66de4bd3b599bd08">
    <img src="/img/workflows-days/w7-input-argument.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f2940eb12fa1533e66de4bd3b599bd08">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/w7-input-argument.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>How do we pass this input argument?
In the execution screen, in the input pane on the left, we create a JSON object, with a firstname and lastname keys.
This JSON object is the dictionary in the person variable of our workflow definition.</p>
<p>In this video, you&rsquo;ll see input arguments in action:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/3dyKx2zBiXA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 6 with Workflows — Arrays and dictionaries</title><link>https://glaforge.dev/posts/2020/12/09/day-6-with-workflows-arrays-and-dictionaries/</link><pubDate>Wed, 09 Dec 2020 22:14:55 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/09/day-6-with-workflows-arrays-and-dictionaries/</guid><description>&lt;p>So far, in this series of articles on &lt;a href="https://cloud.google.com/workflows">Cloud Workflows&lt;/a>,
we have used simple data types, like strings, numbers and boolean values.
However, it&amp;rsquo;s possible to use more complex data structures,
like &lt;a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#arrays">arrays&lt;/a> and
&lt;a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#dictionaries">dictionaries&lt;/a>.
In this new episode, we&amp;rsquo;re going to use those new structures.&lt;/p>
&lt;p>Arrays can be defined inline (like &lt;code>anArray&lt;/code>) or spanning over several lines (like &lt;code>anotherArray&lt;/code>):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#062873;font-weight:bold">assignment&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">assign&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">anArray&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>[&lt;span style="color:#4070a0">&amp;#34;a&amp;#34;&lt;/span>,&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#4070a0">&amp;#34;b&amp;#34;&lt;/span>,&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#4070a0">&amp;#34;c&amp;#34;&lt;/span>]&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">anotherArray&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">            &lt;/span>- &lt;span style="color:#bbb"> &lt;/span>one&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">            &lt;/span>- &lt;span style="color:#bbb"> &lt;/span>two&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>- &lt;span style="color:#062873;font-weight:bold">output&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">return&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>${anArray[0] + anotherArray[1]}&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The output step will return the string &lt;code>&amp;quot;atwo&amp;quot;&lt;/code>.&lt;/p></description><content:encoded>
<![CDATA[<p>So far, in this series of articles on <a href="https://cloud.google.com/workflows">Cloud Workflows</a>,
we have used simple data types, like strings, numbers and boolean values.
However, it&rsquo;s possible to use more complex data structures,
like <a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#arrays">arrays</a> and
<a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#dictionaries">dictionaries</a>.
In this new episode, we&rsquo;re going to use those new structures.</p>
<p>Arrays can be defined inline (like <code>anArray</code>) or spanning over several lines (like <code>anotherArray</code>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">assignment</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">anArray</span>:<span style="color:#bbb">  </span>[<span style="color:#4070a0">&#34;a&#34;</span>,<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;b&#34;</span>,<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;c&#34;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">anotherArray</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#bbb"> </span>one<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>- <span style="color:#bbb"> </span>two<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">output</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${anArray[0] + anotherArray[1]}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The output step will return the string <code>&quot;atwo&quot;</code>.</p>
<p>For dictionaries, you can define them as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">assignment</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">person</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">firstname</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Guillaume&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">lastname</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Laforge&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">age</span>:<span style="color:#bbb">  </span><span style="color:#40a070">43</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">kids</span>:<span style="color:#bbb">  </span>[<span style="color:#4070a0">&#34;Marion&#34;</span>,<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Erine&#34;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">output</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${person.firstname  +  &#34; and &#34;  + person.kids[1]}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The output step will return the string <code>&quot;Guillaume and Erine&quot;</code>.</p>
<p>Notice that we nested an array within a dictionary.
So you can easily create dictionaries containing arrays, containing other dictionaries, etc,
just like any JSON or YAML structures.</p>
<p>In the example we were able to access the second kid of the person, mixing both the field (dot)
and index (square brackets) notations to access fields of our dictionary, and elements of our array.</p>
<p>This video shows both arrays and dictionaries in action:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/9JrqlV5s11Q?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>In the coming articles, we&rsquo;ll see that such data structures are handy for dealing with API endpoint calls.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 5 with Workflows — Visualizing the structure of your workflow definition</title><link>https://glaforge.dev/posts/2020/12/08/day-5-with-workflows-visualizing-the-structure-of-your-workflow-definition/</link><pubDate>Tue, 08 Dec 2020 22:26:13 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/08/day-5-with-workflows-visualizing-the-structure-of-your-workflow-definition/</guid><description>&lt;p>So far, in our Cloud Workflows series, we have seen some of the YAML syntax for defining workflows.
However, steps are defined after each other, as a series of step definitions,
but in spite of the jump instructions, the conditionals,
you don&amp;rsquo;t really see visually what is going to be the next potential step in a workflow execution.&lt;/p>
&lt;p>Fortunately, a new UI enhancement has landed in the Google Cloud Console:
the ability to visualize a workflow definition with a graph, when you&amp;rsquo;re editing the definition.
Furthermore, the graph is updated in quasi real-time as you make updates to the definition.&lt;/p></description><content:encoded>
<![CDATA[<p>So far, in our Cloud Workflows series, we have seen some of the YAML syntax for defining workflows.
However, steps are defined after each other, as a series of step definitions,
but in spite of the jump instructions, the conditionals,
you don&rsquo;t really see visually what is going to be the next potential step in a workflow execution.</p>
<p>Fortunately, a new UI enhancement has landed in the Google Cloud Console:
the ability to visualize a workflow definition with a graph, when you&rsquo;re editing the definition.
Furthermore, the graph is updated in quasi real-time as you make updates to the definition.</p>
<p><figure>
  <a href="#img-cca66c989c43043be376be5d0d856289">
    <img src="/img/workflows-days/w05-graph.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-cca66c989c43043be376be5d0d856289">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/w05-graph.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Let&rsquo;s see this in action in the video below:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/RQ11ATLxf3I?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Thanks to this visualization, it&rsquo;s easier to further understand how your workflow definition is structured, how executions operate. You can more easily track which steps follows a particular step.</p>
<p>Enjoy!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 4 with Workflows — Jumping with switch conditions</title><link>https://glaforge.dev/posts/2020/12/04/day-4-with-workflows-jumping-with-switch-conditions/</link><pubDate>Fri, 04 Dec 2020 22:50:12 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/04/day-4-with-workflows-jumping-with-switch-conditions/</guid><description>&lt;p>In the previous articles about Google &lt;a href="https://cloud.google.com/workflows">Cloud Workflows&lt;/a>,
we talked about how to &lt;a href="https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/">assign variables, create expressions&lt;/a>,
and also how to &lt;a href="https://glaforge.dev/posts/2020/12/02/day-2-with-workflows-a-workflow-is-made-of-steps-and-jumps/">jump from a step to another&lt;/a>.
It&amp;rsquo;s time to combine both aspects to understand how we can do conditional jumps, thanks to the &lt;code>switch&lt;/code> instruction.&lt;/p>
&lt;p>Let&amp;rsquo;s start with a first step defining a variable, whose value we&amp;rsquo;ll use in our switch condition:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#062873;font-weight:bold">assignement&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">assign&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">number&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#40a070">42&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we&amp;rsquo;re going to create our second step that will use a &lt;code>switch&lt;/code> instruction, with an expression:&lt;/p></description><content:encoded>
<![CDATA[<p>In the previous articles about Google <a href="https://cloud.google.com/workflows">Cloud Workflows</a>,
we talked about how to <a href="https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/">assign variables, create expressions</a>,
and also how to <a href="https://glaforge.dev/posts/2020/12/02/day-2-with-workflows-a-workflow-is-made-of-steps-and-jumps/">jump from a step to another</a>.
It&rsquo;s time to combine both aspects to understand how we can do conditional jumps, thanks to the <code>switch</code> instruction.</p>
<p>Let&rsquo;s start with a first step defining a variable, whose value we&rsquo;ll use in our switch condition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">assignement</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">number</span>:<span style="color:#bbb">  </span><span style="color:#40a070">42</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Then we&rsquo;re going to create our second step that will use a <code>switch</code> instruction, with an expression:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">evaluate</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb">  </span>${number  &gt; 100}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>highValue<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb">  </span>${number  &lt;  100}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>lowValue<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb">  </span>end<span style="color:#bbb">
</span></span></span></code></pre></div><p>We define two conditions with two expressions, checking if the number is above or below 100,
then we go to a different step (<code>highValue</code> or <code>lowValue</code> steps).
If none of the conditions are met, we go to the end of the workflow
(or we could return some value or raise some error).</p>
<p>We also need our two steps to go to:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">highValue</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;It&#39;s high!&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">lowValue</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;It&#39;s rather low!&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>And we&rsquo;re done! If the number is <code>42</code>, like in our case,
the execution of the workflow will go through the lowValue step,
and return the string saying that it&rsquo;s a low value.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/thSKszcLWSg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Thanks to <a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#jumps">switch conditionals</a>,
with expressions and jumps, we can have non-linear logic in our workflow definitions.
In upcoming articles, we will also have a look at how to use more complex data structures
like arrays and dictionaries, and how to define inputs and outputs.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 3 with Workflows — Variable assignment and expressions</title><link>https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/</link><pubDate>Thu, 03 Dec 2020 23:00:14 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/03/day-3-with-workflows-variable-assignment-and-expressions/</guid><description>&lt;p>Now that we have multiple steps in our workflow definition,
let&amp;rsquo;s see how we can pass data around, from a step to another.&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-171bae7a2cbbbadea9bc66cb278ca766">
&lt;img src="https://glaforge.dev/img/workflows-days/w03-var-assign.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-171bae7a2cbbbadea9bc66cb278ca766">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/workflows-days/w03-var-assign.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>In a step, you can assign values to variables.
Those values can be ints, doubles, strings, or booleans (and also null).
Use the &lt;code>assign&lt;/code> keyword as follows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#062873;font-weight:bold">assignments&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">assign&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">two&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#40a070">2&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">pi&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#40a070">3.14&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">message&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#4070a0">&amp;#34;Hello&amp;#34;&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">        &lt;/span>- &lt;span style="color:#062873;font-weight:bold">bool&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#007020;font-weight:bold">True&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Those variables are available in the whole scope of the workflow, and can be accessed in other steps.
So let&amp;rsquo;s see how we can do something with those variables.
Let&amp;rsquo;s add a second step to our workflow definition:&lt;/p></description><content:encoded>
<![CDATA[<p>Now that we have multiple steps in our workflow definition,
let&rsquo;s see how we can pass data around, from a step to another.</p>
<p><figure>
  <a href="#img-171bae7a2cbbbadea9bc66cb278ca766">
    <img src="/img/workflows-days/w03-var-assign.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-171bae7a2cbbbadea9bc66cb278ca766">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/w03-var-assign.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In a step, you can assign values to variables.
Those values can be ints, doubles, strings, or booleans (and also null).
Use the <code>assign</code> keyword as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">assignments</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">two</span>:<span style="color:#bbb">  </span><span style="color:#40a070">2</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">pi</span>:<span style="color:#bbb">  </span><span style="color:#40a070">3.14</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">message</span>:<span style="color:#bbb">  </span><span style="color:#4070a0">&#34;Hello&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">bool</span>:<span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">True</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Those variables are available in the whole scope of the workflow, and can be accessed in other steps.
So let&rsquo;s see how we can do something with those variables.
Let&rsquo;s add a second step to our workflow definition:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">twoPi</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb">  </span>${&#34;Twice  pi  is  &#34; + string(two * pi)}<span style="color:#bbb">
</span></span></span></code></pre></div><p>We are using the <code>${}</code> notation to create an expression.
We&rsquo;re multiplying two numbers, we&rsquo;re converting them to a string,
and we&rsquo;re concatenating two strings together, to get our final value.</p>
<p>Note that not all operations are allowed on all types, so you might need to do some conversions
with built-in <a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#conversion_functions">conversion functions</a> like the <code>string()</code> function in our example.
There are all sorts of arithmetic operators or boolean logic operators.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/dygoGp_tcCk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>For more information, you can read about
<a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#assign-step">variable assignments</a>,
<a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#data_types">data types</a>, and
<a href="https://cloud.google.com/workflows/docs/reference/syntax?hl=en#expressions">expressions</a>.
Next time, we&rsquo;ll also have a look at more complex data types.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 2 with Workflows — A workflow is made of steps and jumps</title><link>https://glaforge.dev/posts/2020/12/02/day-2-with-workflows-a-workflow-is-made-of-steps-and-jumps/</link><pubDate>Wed, 02 Dec 2020 23:16:07 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/02/day-2-with-workflows-a-workflow-is-made-of-steps-and-jumps/</guid><description>&lt;p>Let&amp;rsquo;s continue our discovery of &lt;a href="https://cloud.google.com/workflows">Goole Cloud Workflows&lt;/a>!&lt;/p>
&lt;p>Yesterday, we discovered the UI of Workflows.
We &lt;a href="https://glaforge.dev/posts/2020/12/01/day-1-with-workflows-your-first-step-to-hello-world/">created our first workflow&lt;/a>.
We started with a single step, returning a greeting message:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#062873;font-weight:bold">sayHello&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">return&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Hello from Cloud Workflows!&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A workflow definition is made of steps.
But not just one! You can create several steps.
In YAML, the structure of your workflow will be something like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#062873;font-weight:bold">stepOne&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#60a0b0;font-style:italic"># do something&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>- &lt;span style="color:#062873;font-weight:bold">stepTwo&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#60a0b0;font-style:italic"># do something else&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>- &lt;span style="color:#062873;font-weight:bold">sayHello&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">    &lt;/span>&lt;span style="color:#062873;font-weight:bold">return&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Hello from Cloud Workflows!&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By default, steps are executed in the order they appear, from top to bottom.
The execution will finish when either you return a value, or you reach the final step.
If there&amp;rsquo;s no return statement, a null value is returned as result of the workflow execution.&lt;/p></description><content:encoded>
<![CDATA[<p>Let&rsquo;s continue our discovery of <a href="https://cloud.google.com/workflows">Goole Cloud Workflows</a>!</p>
<p>Yesterday, we discovered the UI of Workflows.
We <a href="https://glaforge.dev/posts/2020/12/01/day-1-with-workflows-your-first-step-to-hello-world/">created our first workflow</a>.
We started with a single step, returning a greeting message:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">sayHello</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>Hello from Cloud Workflows!<span style="color:#bbb">
</span></span></span></code></pre></div><p>A workflow definition is made of steps.
But not just one! You can create several steps.
In YAML, the structure of your workflow will be something like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">stepOne</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># do something</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">stepTwo</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#60a0b0;font-style:italic"># do something else</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">sayHello</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>Hello from Cloud Workflows!<span style="color:#bbb">
</span></span></span></code></pre></div><p>By default, steps are executed in the order they appear, from top to bottom.
The execution will finish when either you return a value, or you reach the final step.
If there&rsquo;s no return statement, a null value is returned as result of the workflow execution.</p>
<p>A small step for a workflow execution, but you can also do a jump between steps!
For that, you&rsquo;ll use the next instruction:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">stepOne</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>stepTwo<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">stepThree</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>sayHello<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">stepTwo</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>stepThree<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">sayHello</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>Hello from Cloud Workflows!<span style="color:#bbb">
</span></span></span></code></pre></div><p>Here, we jump between steps, back and forth, before going to the final step that will return a value, and thus finish the execution of our workflow.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/BTzb1m5pDXI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Of course, we can go beyond a linear series of steps, and in subsequent articles,
we&rsquo;ll see how we can create conditional jumps and switches, for more complex logic,
and how we can pass some data and values between steps.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Day 1 with Workflows — Your first step to Hello World</title><link>https://glaforge.dev/posts/2020/12/01/day-1-with-workflows-your-first-step-to-hello-world/</link><pubDate>Tue, 01 Dec 2020 23:22:11 +0100</pubDate><guid>https://glaforge.dev/posts/2020/12/01/day-1-with-workflows-your-first-step-to-hello-world/</guid><description>&lt;p>With more and more interconnected services, making sense of their interactions becomes critical.
With Google &lt;a href="https://cloud.google.com/workflows">Cloud Workflows&lt;/a>,
developers can orchestrate and automate such complex systems by creating serverless workflows.&lt;/p>
&lt;p>In this series of articles, we will learn together how to use Goole Cloud Workflows,
and get to know all its features, with short and easy to read tutorials.
For our first day, we&amp;rsquo;ll discover and use the Workflows UI in the
&lt;a href="https://console.cloud.google.com/">cloud console&lt;/a>.
We will create a simple &amp;ldquo;hello world&amp;rdquo; workflow, consisting of a simple step.
Going further, in the coming days, we&amp;rsquo;ll learn more about advanced features.
But first things first!&lt;/p></description><content:encoded>
<![CDATA[<p>With more and more interconnected services, making sense of their interactions becomes critical.
With Google <a href="https://cloud.google.com/workflows">Cloud Workflows</a>,
developers can orchestrate and automate such complex systems by creating serverless workflows.</p>
<p>In this series of articles, we will learn together how to use Goole Cloud Workflows,
and get to know all its features, with short and easy to read tutorials.
For our first day, we&rsquo;ll discover and use the Workflows UI in the
<a href="https://console.cloud.google.com/">cloud console</a>.
We will create a simple &ldquo;hello world&rdquo; workflow, consisting of a simple step.
Going further, in the coming days, we&rsquo;ll learn more about advanced features.
But first things first!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/75BekrpL-qo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>In the Google Cloud console UI, you can locate Workflows in the <code>Tools</code> section of the hamburger menu:</p>
<p><figure>
  <a href="#img-60149b082263ff11f1ae2750f2490229">
    <img src="/img/workflows-days/wf01-01-menu.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-60149b082263ff11f1ae2750f2490229">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-01-menu.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You can pin this menu item, to have it stick at the top of the list.</p>
<p>The first time you are accessing this page, you&rsquo;ll be greeted with the following screen,
which will ask you to enable the Workflows API. So just click on <code>ENABLE</code>:</p>
<p><figure>
  <a href="#img-dc1f38ca7ed02022389744b18e2f5400">
    <img src="/img/workflows-days/wf01-02-enable-api.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-dc1f38ca7ed02022389744b18e2f5400">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-02-enable-api.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Once the API is enabled, you&rsquo;ll be in the Workflows home screen:</p>
<p><figure>
  <a href="#img-f1e80d1bfc330fec34744645ab091128">
    <img src="/img/workflows-days/wf01-03-workflows-home.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f1e80d1bfc330fec34744645ab091128">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-03-workflows-home.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Click on the <code>CREATE</code> button to create your first workflow definition:</p>
<p><figure>
  <a href="#img-6a1bb88ece1ed976a0037f05325e4975">
    <img src="/img/workflows-days/wf01-04-empty-state.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6a1bb88ece1ed976a0037f05325e4975">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-04-empty-state.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Find a name for your workflow (it should start with a letter).
You can provide an optional description. Currently, only &ldquo;us-central1&rdquo;
is available for the beta of Workflows, but more regions will be available later on.</p>
<p>Notice that we have to select a service account that Workflows will use to call other Google Cloud APIs, however here, there&rsquo;s a warning telling us that the project requires a service account.
As I&rsquo;ve created a brand new project, I didn&rsquo;t have any service account created.
If you had used, for example, Cloud Functions beforehand, a default service account would have been created. If you need to create a service account,
you can create one in <code>IAM &amp; Admin &gt; Service Accounts</code>, then use this one.</p>
<p>My first workflow will be called <code>w01-first-workflow</code>:</p>
<p><figure>
  <a href="#img-509c9b6d1c89ed9c28434a4a3a88391f">
    <img src="/img/workflows-days/wf01-05-form-filled.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-509c9b6d1c89ed9c28434a4a3a88391f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-05-form-filled.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Move on to the next section with the <code>NEXT</code> button.
That&rsquo;s where you will define your workflow:</p>
<p><figure>
  <a href="#img-c0ee888a16245a5a70d46f46ba356db3">
    <img src="/img/workflows-days/wf01-06-definition.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c0ee888a16245a5a70d46f46ba356db3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-06-definition.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>This first workflow consists in one single step, called <code>sayHello</code>,
and whose sole purpose is to return a hello world message:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">sayHello</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>Hello from Cloud Workflows!<span style="color:#bbb">
</span></span></span></code></pre></div><p>As you can see, workflow definitions are written using the
<a href="https://yaml.org/">YAML</a> configuration language.</p>
<p>Click <code>DEPLOY</code> to deploy the workflow. You will then see the details of your new workflow.
In the &ldquo;executions&rdquo; tab, you can see past executions.</p>
<p><figure>
  <a href="#img-81646faa8d91ac8a075f774f2922785c">
    <img src="/img/workflows-days/wf01-07-created.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-81646faa8d91ac8a075f774f2922785c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-07-created.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In the <code>logs</code> section, you can see the logging messages associated with your workflow creation, deployment and executions:</p>
<p><figure>
  <a href="#img-0c2cbbcd13ad04cf497516748473a757">
    <img src="/img/workflows-days/wf01-08-logs.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0c2cbbcd13ad04cf497516748473a757">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-08-logs.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And in the <code>definitions</code> section, you can see the YAML description you just created:</p>
<p><figure>
  <a href="#img-71178e72cf21aa29b0a7428058bb499d">
    <img src="/img/workflows-days/wf01-09-definition.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-71178e72cf21aa29b0a7428058bb499d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-09-definition.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Now click on the <code>EXECUTE</code> button. You will see the input section
(we&rsquo;ll learn about input arguments in an upcoming article), and the YAML definition.
Click the other <code>EXECUTE</code> button:</p>
<p><figure>
  <a href="#img-b86caa1790e97997d68b79a3a114d094">
    <img src="/img/workflows-days/wf01-10-exec-screen.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b86caa1790e97997d68b79a3a114d094">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-10-exec-screen.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You will see the result of your execution (<code>succeeded</code>, with other details about the execution),
as well as both the input and the output, with our greeting message:</p>
<p><figure>
  <a href="#img-b87219fa2fc75baf9936a6f1d88d1705">
    <img src="/img/workflows-days/wf01-11-executed.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b87219fa2fc75baf9936a6f1d88d1705">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/workflows-days/wf01-11-executed.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And here you are! You created your first workflow definition, and launched the first execution of this workflow!</p>
<p>In the coming days, we will have a closer look at the structure of workflow definitions (its steps),
how to define input arguments, but also how to create an execution of a workflow from the command-line.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Orchestrating microservices with Google Cloud Workflows</title><link>https://glaforge.dev/posts/2020/11/18/orchestrating-microservices-with-cloud-workflows/</link><pubDate>Wed, 18 Nov 2020 18:56:08 +0100</pubDate><guid>https://glaforge.dev/posts/2020/11/18/orchestrating-microservices-with-cloud-workflows/</guid><description>&lt;p>The trend toward splitting a monolith into fine-grained loosely-coupled microservices has its merits.
It allows us to scale parts of an application more easily. Teams become more effective on their focused perimeter.
However, in a chain or graph of services interacting with each other via message buses or other eventing mechanisms,
it becomes difficult to understand when things start to break.
Your business processes spanning those services are in limbo.
Here starts detective work to find out how to get back on track.&lt;/p></description><content:encoded>
<![CDATA[<p>The trend toward splitting a monolith into fine-grained loosely-coupled microservices has its merits.
It allows us to scale parts of an application more easily. Teams become more effective on their focused perimeter.
However, in a chain or graph of services interacting with each other via message buses or other eventing mechanisms,
it becomes difficult to understand when things start to break.
Your business processes spanning those services are in limbo.
Here starts detective work to find out how to get back on track.</p>
<p><strong>Choreography</strong>: like a bunch of dancers on the floor composing a ballet.
Loosely-coupled microservices compose business processes without really being aware of each other,
casually interacting by receiving and sending messages or events.</p>
<p><strong>Orchestration</strong>: more like a conductor of an orchestra who directs musicians and their instruments to play each part.
The approach of using a higher level solution that purposefully invokes and tracks each individual service,
enables developers to know what the current state of a business process is.</p>
<p>Both approaches have their pros and cons. The loosely-coupled aspects of choreography certainly enables agility.
But business processes are harder to follow.
Although orchestration adds a single-point-of-failure with its orchestrator tying all the pieces together,
it brings clarity in the spaghetti of myriads of microservices.</p>
<p>In addition to GCP&rsquo;s existing messaging (<a href="https://cloud.google.com/pubsub/">Cloud Pub/Sub</a>)
and eventing solutions (<a href="https://cloud.google.com/blog/products/serverless/build-event-driven-applications-in-cloud-run">Eventarc</a>)
for your service choreography, the newly launched product <a href="https://cloud.google.com/workflows">Cloud Workflows</a> 
is tackling the orchestration approach.</p>
<p>Cloud Workflows is a scalable fully-managed serverless system that automates and coordinates services,
takes care of error handling and retries on failure, and tells you if the overall process has finished.</p>
<p>In this short video, during the &ldquo;demo derby&rdquo; at Google Cloud Next OnAir,
I had the chance to present a demo of Cloud Workflows, with some concrete examples:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/_4fo_u5rY_8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>In this video, I started with the proverbial Hello World, using the Yaml syntax for defining workflows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">hello</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Hello from Cloud Workflows!&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>I defined a <code>hello</code> step, whose sole purpose is to return a string, as the result of its execution.</p>
<p>Next, I showed that workflow definitions can take arguments, and also return values thanks to more complex expressions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">main</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">params</span>:<span style="color:#bbb"> </span>[args]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">steps</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">returnGreeting</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${&#34;Hello &#34; + args.first + &#34; &#34; + args.last}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Cloud Workflows is able to invoke any HTTP-based service (and supports OAuth2 and OIDC),
whether in Google Cloud or outside (on premises, or other servers).
Here, I invoke 2 Cloud Functions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">getRandomNumber</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>https://us-central1-myprj.cloudfunctions.net/randomNumber<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>randomNumber<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">getNthPoemVerse</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>https://us-central1-myprj.cloudfunctions.net/theCatPoem<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">query</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">nth</span>:<span style="color:#bbb"> </span>${randomNumber.body.number}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>randomVerse<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">returnOutput</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${randomVerse.body}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>getRandomNumber</code> step calls a function that returns a random number with an HTTP GET,
and stores the result of that invocation in the <code>randomNumber</code> variable.</p>
<p>The <code>getNthPoemVerse</code> calls another function that takes a query parameter,
which is found in the <code>randomNumber</code> variable which holds the result of the previous function invocation.</p>
<p>The <code>returnOutput</code> step then returns the resulting value.</p>
<p>My fourth example shows variable assignment and conditional switches in action:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#062873;font-weight:bold">getRandomNumber</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">call</span>:<span style="color:#bbb"> </span>http.get<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">url</span>:<span style="color:#bbb"> </span>https://us-central1-myprj.cloudfunctions.net/randomNumber<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">result</span>:<span style="color:#bbb"> </span>randomNumber<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">assign_vars</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">assign</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">number</span>:<span style="color:#bbb"> </span>${int(randomNumber.body.number)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">conditionalSwitch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb"> </span>${number &lt; 33}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>low<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#062873;font-weight:bold">condition</span>:<span style="color:#bbb"> </span>${number &lt; 66}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>medium<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">next</span>:<span style="color:#bbb"> </span>high<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">low</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${&#34;That&#39;s pretty small! &#34; + string(number)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">medium</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${&#34;Hmm, okay, an average number. &#34; + string(number)}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#062873;font-weight:bold">high</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">return</span>:<span style="color:#bbb"> </span>${&#34;It&#39;s a big number! &#34; + string(number)}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Reusing the random function from the previous example, notice how variables are assigned,
and how to create a switch with multiple conditions,
as well as showing how to redirect the execution of the workflow to different steps, depending on the outcome of the switch.</p>
<p>But there&rsquo;s really more to this! You can double check the <a href="https://cloud.google.com/workflows/docs/reference/syntax">syntax reference</a>,
to see all the constructs you can use in your workflow definitions.</p>
<h3 id="summary">Summary</h3>
<p>Cloud Workflows:</p>
<ul>
<li>Orchestrate Google Cloud and HTTP-based API services into serverless workflows</li>
<li>Automate complex processes</li>
<li>Fully managed service requires no infrastructure or capacity planning</li>
<li>Fast scalability supports scaling down to zero and pay-per-use pricing model</li>
</ul>
<p>In terms of features:</p>
<ul>
<li>Reliable workflow execution</li>
<li>Built-in error handling</li>
<li>Passing variable values between workflow steps</li>
<li>Built-in authentication for Google Cloud products</li>
<li>Low latency of execution</li>
<li>Support for external API calls</li>
<li>Built-in decisions and conditional step executions</li>
<li>Cloud Logging</li>
</ul>
<p>If you want to get started with <a href="https://cloud.google.com/workflows">Cloud Workflows</a>, you can head over to this <a href="https://codelabs.developers.google.com/codelabs/cloud-workflows-intro#0">hands-on codelabs</a> from my colleague <a href="https://twitter.com/meteatamel">Mete Atamel</a>. Learn more by watching this <a href="https://www.youtube.com/watch?v=Uz8G8fTwwXs">longer video</a> by Product Manager Filip Knapik who dives into Cloud Workflows. In upcoming articles, we&rsquo;ll come back to Workflows into more details, diving into some more advanced features, or how to migrate a choreographed example, into an orchestrated one. So, stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The developer advocacy feedback loop</title><link>https://glaforge.dev/talks/2020/08/06/the-developer-advocacy-feedback-loop/</link><pubDate>Thu, 06 Aug 2020 19:11:17 +0100</pubDate><guid>https://glaforge.dev/talks/2020/08/06/the-developer-advocacy-feedback-loop/</guid><description>&lt;p>For one of the &lt;a href="https://2020.devrel.net/speaker/guillaume-laforge/">closing keynotes&lt;/a> of &lt;a href="https://2020.devrel.net/">DevRelCon Earth 2020&lt;/a>, I spoke about what I call the Developer Advocacy Feedback Loop. People often think about developer relations and advocacy as just being about external outreach. However, there&amp;rsquo;s more to it! Developer Advocates are here to represent users, developers, technical practitioners, to influence the roadmap and development of the services and products to suit their needs. That&amp;rsquo;s the internal advocacy that loops back into improving the products.&lt;/p></description><content:encoded>
<![CDATA[<p>For one of the <a href="https://2020.devrel.net/speaker/guillaume-laforge/">closing keynotes</a> of <a href="https://2020.devrel.net/">DevRelCon Earth 2020</a>, I spoke about what I call the Developer Advocacy Feedback Loop. People often think about developer relations and advocacy as just being about external outreach. However, there&rsquo;s more to it! Developer Advocates are here to represent users, developers, technical practitioners, to influence the roadmap and development of the services and products to suit their needs. That&rsquo;s the internal advocacy that loops back into improving the products.</p>
<p>Without further ado, let me share with you the slide deck here:</p>
<script async class="speakerdeck-embed" data-id="c9aa5be6e7534443963a5b981ef0b713" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>Let me paraphrase what I presented in this talk.</p>
<p>For the past 4 years, I&rsquo;ve been a Developer Advocate, for Google, focusing on Google Cloud, and especially our serverless solutions (like App Engine, Cloud Functions, Cloud Run). I fell in the magic potion of advocacy, inadvertently, a long time ago while working on an open source project. This project is the <a href="http://groovy-lang.org/">Apache Groovy</a> programming language. I was leading the project, but at the same time, I was also evangelising it at events, through articles,, and was trying to incorporate the feedback I was getting in the field back into the project. I was doing advocacy without really realizing it, like <a href="https://literature.stackexchange.com/questions/11844/meaning-of-the-prose-of-monsieur-jourdain">Mr Jourdain</a> in Molière&rsquo;s play who was speaking in prose without knowing it. But I really saw a loop, a feedback loop, in the process, in how you spread the word about technologies, but also how you can listen to the feedback and improve your product.</p>
<p><figure>
  <a href="#img-376110755c13ec92e741087841ee6559">
    <img src="/img/developer-advocacy-feedback-loop/01&#43;-&#43;feedback&#43;loop.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-376110755c13ec92e741087841ee6559">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/01&#43;-&#43;feedback&#43;loop.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>If you&rsquo;ve studied Electronics, you might have seen such diagrams about the feedback loop. There&rsquo;s something in input, something in output, but there&rsquo;s a loop back, that brings some output back into the input channel. To make the parallel with advocacy&hellip; Advocacy is not just a one-way monologue, it&rsquo;s a conversation: you&rsquo;re here to tell a story to your kids for example, but you listen to feedback from the audience, on how to make your story even better. Not just how you tell the story (better intonation, pauses), but really improving the plot, the characters, the setting,≈‹ everything, perhaps making up a totally different story in the end!</p>
<p><figure>
  <a href="#img-440569dbe95aa7bea414b513d8c71f21">
    <img src="/img/developer-advocacy-feedback-loop/03&#43;-&#43;devrel&#43;multitude.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-440569dbe95aa7bea414b513d8c71f21">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/03&#43;-&#43;devrel&#43;multitude.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Let me start with a short disclaimer. If you ask this room to give a definition of developer relations, or developer advocacy, or evangelism (a term I avoid because of its connotations), you&rsquo;ll get as many answers as there are attendees. I don&rsquo;t claim I have THE ultimate definitions for these concepts and approaches. I don&rsquo;t claim those things are the same things, or are different. And anyway, there&rsquo;s not just one way to do it, there&rsquo;s a multitude of ways. They are many things we do the same way, but I&rsquo;m sure there are many incredible things you do that I&rsquo;m not even aware of but that I&rsquo;d like to learn more about! But I&rsquo;ll tell you how I am doing developer advocacy, and where this feedback loop comes into play.</p>
<p><figure>
  <a href="#img-45ecc9c0db6af982484863f08cb0d693">
    <img src="/img/developer-advocacy-feedback-loop/02&#43;-&#43;who&#43;are&#43;we.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-45ecc9c0db6af982484863f08cb0d693">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/02&#43;-&#43;who&#43;are&#43;we.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>So, who are we? DevRel is not always the same thing everywhere, in every company. And there&rsquo;s not just one way to do DevRel.</p>
<p>Are we salespeople? Not necessarily, I don&rsquo;t get any bucks when I indirectly help sign a new customer deal. My metrics are more about the number of developers reached, or views on my articles or videos, or Twitter impressions.</p>
<p>So are we marketing people? Well, I have some similar metrics for sure, I advertise the products or company I represent, but my goal is that my audience (the technical practitioners) be successful, even if they end up not using my technology. I want my audience to even advocate themselves for those products if possible (if the product is good and makes sense for them).</p>
<p>Are we engineers? In my case, yes I am, I&rsquo;m even in the Engineering org chart, and to show more empathy towards our engineer users, it&rsquo;s easier if we&rsquo;re engineers ourselves. We speak the same language. We&rsquo;re part of the same community. We have the same tool belt. Also as an engineer, I can even sometimes contribute to the products I talk about. But it&rsquo;s not because you&rsquo;re not an engineer that you can&rsquo;t succeed, and be a good advocate! Empathy is really key in this role, more so probably than engineering chops.</p>
<p>Or are we PMs? In a previous life, in a small startup, I was actually wearing 2 hats: PM &amp; DA. But it&rsquo;s tough to do two jobs like these at the same time. As a DA (without being a PM), with my contributions, my feedback from the field, from the community I advocate for, I do influence the roadmap of our products, for sure. But I&rsquo;m only a part of the equation. However providing critical product feedback is super important in my job. That&rsquo;s the key aspect of the developer advocacy feedback loop!</p>
<p>Perhaps we&rsquo;re just international travelers? We&rsquo;re measured by the number of visa stamps on our passports? Ah well, maybe. Or maybe not, we try to be greener, but with COVID-19, things have changed recently! The pandemic refines our job, our duties, our ways to communicate. There&rsquo;s lots we can do in the comfort of our home office too.</p>
<p>Ultimately, we&rsquo;re all different, but we all have myriads of ways to contribute and reach our goal. Some of us may be focusing more on awesome videos tutorials, some on organizing hours-long hackathons, and others will be awesome beta-testers for our products, write cristal-clear code samples or SDKs, etc. There&rsquo;s not just one way to be a great Developer Advocate. You don&rsquo;t need to do it all. And we&rsquo;re a team. So we complement each other with our respective strengths. And we work with others too, like marketing, sales, consulting, tech writers, leadership.</p>
<p><figure>
  <a href="#img-d24a02d2318f4f4c8f86087620a0c174">
    <img src="/img/developer-advocacy-feedback-loop/04&#43;-&#43;our&#43;goal.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d24a02d2318f4f4c8f86087620a0c174">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/04&#43;-&#43;our&#43;goal.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>What do we do, what&rsquo;s our goal? We are empowering our users to reach their goals. We want to make them successful. We&rsquo;re enabling customer success. We&rsquo;re driving mindshare in the field, in our communities. We are making our users happy!</p>
<p><figure>
  <a href="#img-e74dd89e8d2067cda3997db28b3a0d39">
    <img src="/img/developer-advocacy-feedback-loop/05&#43;-&#43;our&#43;tools.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e74dd89e8d2067cda3997db28b3a0d39">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/05&#43;-&#43;our&#43;tools.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>How do we make our community, our users, our customers be successful? There are many tools for that. Some of the most well-known tools that we can use are outward facing: it&rsquo;s about external outreach (talks, articles, videos, etc.) But to make our communities more successful, we also need to get our products improved. That&rsquo;s where we create the feedback loop, with our internal influence, thanks to some tools I&rsquo;ll enumerate, we can help make the products better, by bringing our users&rsquo; feedback up the chain to the PMs, Product Leads, etc. Let me show you.</p>
<p><figure>
  <a href="#img-ada68f15f833cffb5caa977dfc0e2d56">
    <img src="/img/developer-advocacy-feedback-loop/06&#43;-&#43;the&#43;advocacy&#43;loop.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ada68f15f833cffb5caa977dfc0e2d56">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/06&#43;-&#43;the&#43;advocacy&#43;loop.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Let me introduce you to our personas of my story, of my feedback loop.</p>
<p><figure>
  <a href="#img-1e5bc58f4bff39a778dedbff0a3fc1f2">
    <img src="/img/developer-advocacy-feedback-loop/07&#43;-&#43;the&#43;personnas.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1e5bc58f4bff39a778dedbff0a3fc1f2">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/07&#43;-&#43;the&#43;personnas.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>At the top, you have the product leadership, the PM, CxOs, the SWEs. At the bottom, that&rsquo;s our users, our customers, our technical practitioners And in the middle, in between, there&rsquo;s you, the Developer Advocate.</p>
<p><figure>
  <a href="#img-0291b7e4fd994c0c4836dfc534975b57">
    <img src="/img/developer-advocacy-feedback-loop/08&#43;-&#43;company.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0291b7e4fd994c0c4836dfc534975b57">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/08&#43;-&#43;company.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>But in a way, there are two teams. Here, in the white cloud, at the top, that&rsquo;s your company.</p>
<p><figure>
  <a href="#img-d6fa04b397fabf5cdebeca4839e24e53">
    <img src="https://glaforge.appspot.com/media/09&#43;-&#43;community.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d6fa04b397fabf5cdebeca4839e24e53">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://glaforge.appspot.com/media/09&#43;-&#43;community.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>But at the bottom, that&rsquo;s your community, with your users. You&rsquo;re not just part of the company, you&rsquo;re also part of the community. You are the advocate for your users, representing them to the product leadership, so that their voice is being heard!</p>
<p><figure>
  <a href="#img-64bf38d5486fe42f0e3f023c32dbd1de">
    <img src="/img/developer-advocacy-feedback-loop/10&#43;-&#43;external&#43;outreach.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-64bf38d5486fe42f0e3f023c32dbd1de">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/10&#43;-&#43;external&#43;outreach.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>That&rsquo;s the external outreach. What some call evangelism, the outward part of the loop. You&rsquo;re the voice of the company. You spread the word on your cool technology. You&rsquo;re creating great demos, code samples, polished videos. You&rsquo;re writing helpful articles, useful tutorials, readable documentation. You&rsquo;re attending and presenting at events to talk about the products. You&rsquo;re helping users succeed by answering questions on social media, StackOverflow, or other forums.</p>
<p><figure>
  <a href="#img-d3c22faa69fba5dfc1003df2e27c7fe6">
    <img src="/img/developer-advocacy-feedback-loop/11&#43;-&#43;internal&#43;advocacy.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d3c22faa69fba5dfc1003df2e27c7fe6">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/11&#43;-&#43;internal&#43;advocacy.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>What makes it a feedback loop is this part. It&rsquo;s not just a by-product of the external outreach. It&rsquo;s an integral part of the advocacy work. There&rsquo;s the obvious stuff like filing bugs, or being a customer zero by testing the product before it launches. But things like writing trip reports, friction logs, customer empathy sessions may be new to you. If you can, make it a habit to produce such artifacts. And you can list, and track, and report about all those feedback elements that you bring upstream, and check how it&rsquo;s being enacted or not.</p>
<p>Often people think about us mostly for the outreach part, the arrow going downward toward our community. They can think we&rsquo;re just kind of marketing puppets. And I&rsquo;ve seen conference organisers complaining they only got &ldquo;evangelists&rdquo; at their show, when they wanted &ldquo;real engineers&rdquo; instead, working on the products or projects. But frankly, they are not necessarily always the best at explaining their own projects! Folks often forget that we&rsquo;re here to make them successful, and report their feedback, their needs, to advocate for them, and to influence the decision makers to make better products that fill the needs of those users. Both parts are critical! And please pay attention to that feedback loop, to that arrow going back to the top of the slide, to the leadership.</p>
<p>So let&rsquo;s see some concrete examples of the things you can put in place to provide feedback, and show that DevRel is important and has a strong impact.</p>
<p><figure>
  <a href="#img-9a825926c49d64cb60d021fdc3327855">
    <img src="/img/developer-advocacy-feedback-loop/12&#43;-&#43;friction&#43;logs.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9a825926c49d64cb60d021fdc3327855">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/12&#43;-&#43;friction&#43;logs.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>To make developers happy, you need to remove as much friction as possible. You want the developer experience to be as smooth as possible. You might need to work with UX designers and PMs directly for that. But you can also report about your findings, where you saw friction by writing a friction log. Last week, my colleague Emma <a href="https://www.youtube.com/watch?v=765wLWVcyS0">spoke about this</a> at DevRelCon Earth, and another great colleague, Aja, wrote about <a href="https://devrel.net/developer-experience/an-introduction-to-friction-logging">friction logging</a> on DevRel.net a while ago. Great resources to check out!</p>
<p><figure>
  <a href="#img-b764d65ee0958f30975c49ce3048f844">
    <img src="/img/developer-advocacy-feedback-loop/13&#43;-&#43;example&#43;friction&#43;log.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b764d65ee0958f30975c49ce3048f844">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/13&#43;-&#43;example&#43;friction&#43;log.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I&rsquo;m going to show you a real friction log. There&rsquo;s some metadata about the environment, date, user name, scenario title, etc. You&rsquo;re reporting about some concrete use case you were trying to implement (an app you were building, a new API you were trying to use, etc.) You document all the steps you followed, and tell what worked or not, how you expected things to work out. This document will be shared broadly via an alias which pings most PMs, tech leads, etc. So it&rsquo;s very visible.</p>
<p><figure>
  <a href="#img-0b3594fccdf53abf6639dbc4ed6acf9b">
    <img src="/img/developer-advocacy-feedback-loop/14&#43;-&#43;friction&#43;log&#43;color&#43;coding.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0b3594fccdf53abf6639dbc4ed6acf9b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/14&#43;-&#43;friction&#43;log&#43;color&#43;coding.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>But the key thing here is the color coding aspect. You show where there&rsquo;s friction, where there&rsquo;s frustration, where you&rsquo;d quit if you were a real user. But also, it&rsquo;s super important to highlight what worked well, what surprised you, what delighted you. It&rsquo;s not just about the negative things.</p>
<p><figure>
  <a href="#img-21780f32de53a35a5204ebc93ee18e42">
    <img src="/img/developer-advocacy-feedback-loop/15&#43;-&#43;tagging&#43;stakeholders.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-21780f32de53a35a5204ebc93ee18e42">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/15&#43;-&#43;tagging&#43;stakeholders.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And the last trick to make it effective: add comments, tagging key stakeholders (PMs, Tech Leads, etc), so they really acknowledge the problem. Create associated bug requests, and track them to check if progress is made.</p>
<p><figure>
  <a href="#img-32910b0e9e2c7dd5fbbf5601a3a726dd">
    <img src="/img/developer-advocacy-feedback-loop/16&#43;-&#43;friction&#43;vlog.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-32910b0e9e2c7dd5fbbf5601a3a726dd">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/16&#43;-&#43;friction&#43;vlog.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>My colleague <a href="https://twitter.com/ZackAkil">Zack</a> even developed an application for creating friction vlogs. Video friction logs! With a video, you can show concretely your frustration (but perhaps don&rsquo;t swear too much). A video shows where you struggle, where you lose time. You can navigate to various sections in the video, and annotate those sections, with the green / orange / red color coding scheme. The tool also creates a classical written friction log document as well. I found that application pretty neat, to be honest, especially as it also shows where users struggle, where they lose time.</p>
<p><figure>
  <a href="#img-e8f56fddb0a4deb81116871d7f6be4fa">
    <img src="/img/developer-advocacy-feedback-loop/17&#43;-&#43;advocacy&#43;reporting.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e8f56fddb0a4deb81116871d7f6be4fa">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/17&#43;-&#43;advocacy&#43;reporting.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You can apply the same approach to other kinds of reporting activities. We often write reports for our trips, events, meetups, customer engagements. In particular, although we&rsquo;re not sales people, we&rsquo;re trying to show that we also have an impact on sales. Customers love having DevRel people come and show cool stuff! And we can collect and show the feedback coming from the field to the leadership. It&rsquo;s not just us sharing our own impressions and ideas, it&rsquo;s really coming from someone else&rsquo;s mouth, so it has more weight in the conversation. I&rsquo;d like to highlight our internal advocacy reporting: we have someone on the team that collects all our bug reports (and included them in bug hotlists), all our friction logs, our trip reports, and who actively tracks how this feedback is taken into account, and it&rsquo;s a very effective way of showing that we do have impact to the leadership, beyond the usual metrics. And by the way, even those DevRel product feedback reports make use of the color coding we have in friction logs. So it&rsquo;s a very familiar thing for all our engineering team.</p>
<p><figure>
  <a href="#img-756f63e3eeebd98514eb10432b01f74d">
    <img src="/img/developer-advocacy-feedback-loop/18&#43;-&#43;customer&#43;empathy&#43;sessions.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-756f63e3eeebd98514eb10432b01f74d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/18&#43;-&#43;customer&#43;empathy&#43;sessions.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Another interesting thing we&rsquo;re running is what we call customer empathy sessions, a concept invented by my colleague <a href="https://twitter.com/kmbannerman">Kim</a>. Gather various PMs, SWEs, DevRel people, potentially customers but not mandatory, in the same room (or virtually nowadays) and craft some concrete scenarios of something you&rsquo;d like them to build in small groups (but where you know there&rsquo;s gonna be lots of friction). With teams of 3 or more, each one has a role: a driver, a scribe, and a searcher. Have them do the task. Then compare copies at the end. It&rsquo;s a bit like creating new Critical User Journeys that have not been addressed, that exhibit a lot of friction. But this time the engineers, the PM, will really feel the very same frustration our customers can potentially feel when they can&rsquo;t accomplish their tasks. The various teams often work in silos, on a particular aspect, and avoid certain paths (when you know you shouldn&rsquo;t click somewhere, you won&rsquo;t do it, you&rsquo;ll use the other path you know works). But customer empathy sessions are here to show what our users have to go through in real scenarios, beyond a handful of critical journeys. In summary, feel the pain, and show empathy toward your customers! Really, I won&rsquo;t stress this enough, but empathy is key here, and a real driver for positive change.</p>
<p><figure>
  <a href="#img-8041af831632f909b898a36206cde24a">
    <img src="/img/developer-advocacy-feedback-loop/19&#43;-&#43;hallway&#43;track&#43;office&#43;hours.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8041af831632f909b898a36206cde24a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/19&#43;-&#43;hallway&#43;track&#43;office&#43;hours.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>We can do scalable advocacy, by creating things like videos that are broadcasted to many thousands of watchers. That have a long shelf time, which is less ephemeral than a conference talk. But sometimes, it&rsquo;s also good to do things that actually don&rsquo;t scale. Helping one single person can make a big difference: at a conference after my talk, I had a long conversation with an attendee that had a particular need. I onboarded them on our early access program for a new product, which seemed to be what they needed. They could provide key feedback to our PMs and engineers. And they helped us get that new product ready with a real use case. And the next year, the attendee was a key customer, that even came on stage to talk about the product. So I both won a new customer and a new advocate for that product. So the hallway track at events is very important. And that&rsquo;s the kind of feedback signals I&rsquo;m missing in those times of pandemic.</p>
<p>Another approach is office hours: you set up some time slots in your calendar, and anyone can book time with you. That&rsquo;s a great way to get feedback, and see what problems users are facing. I haven&rsquo;t tried that myself, as I&rsquo;m a bit shy, and afraid someone would ask questions on topics I don&rsquo;t know much about! But that&rsquo;s very effective, and I have several colleagues doing that, and who are learning along the way.</p>
<p><figure>
  <a href="#img-a265a4f94b8b37c5772ad5277ea76d2c">
    <img src="/img/developer-advocacy-feedback-loop/20&#43;-&#43;create&#43;new&#43;products.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a265a4f94b8b37c5772ad5277ea76d2c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/20&#43;-&#43;create&#43;new&#43;products.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Sometimes, your community, your users, will highlight a missing gap in your product portfolio. And it might give you some ideas of a product that would delight those persons, and they could become customers if you had that product. So that&rsquo;s actually how some of my colleagues went on creating totally new products, for example for gaming companies, or for secret management. On another occasion, as I had strong ideas on how a new product runtime should look like, I went on designing and prototyping an API that our users would use. Somehow, it&rsquo;s a bit like being the change you want to see in the world! And the API I designed, further improved with the engineering team, is now an API our customers are using today.</p>
<p><figure>
  <a href="#img-503378d1a9c659bd4ef6b9f776e76d86">
    <img src="/img/developer-advocacy-feedback-loop/21&#43;-&#43;pudding.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-503378d1a9c659bd4ef6b9f776e76d86">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/21&#43;-&#43;pudding.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Time to wrap up. Often, the proof is in the pudding. It&rsquo;s not just about our intuitions or own personal experience. You need to gather feedback, in particular concrete customer feedback, to prove that you&rsquo;re right. And when it&rsquo;s a customer with some money to spend, usually product leadership listens.</p>
<p><figure>
  <a href="#img-f3bcceb9a0fa8a06c4e52140d58cb477">
    <img src="/img/developer-advocacy-feedback-loop/22&#43;-&#43;roses.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f3bcceb9a0fa8a06c4e52140d58cb477">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/22&#43;-&#43;roses.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Sometimes, it&rsquo;s all roses and bloom! Our feedback, ideas, features are implemented! Woohoo! Success!</p>
<p><figure>
  <a href="#img-46cb6bdcdb21fbb48dfac96daae74a85">
    <img src="/img/developer-advocacy-feedback-loop/23&#43;-&#43;brick&#43;wall.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-46cb6bdcdb21fbb48dfac96daae74a85">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/23&#43;-&#43;brick&#43;wall.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>There&rsquo;s the ideal world where we indeed influence products, but sometimes, we also hit a brick wall, a dead end, we&rsquo;re not at the helm, and our feedback is not taken into account. Our companies can be big, work in silos, and it&rsquo;s sometimes a struggle to find the right people who are able to listen to us, and are able to get change enacted. Be resilient, let it not affect you personally, but return to the charge if you really think it&rsquo;s important for your community!</p>
<p><figure>
  <a href="#img-ada68f15f833cffb5caa977dfc0e2d56">
    <img src="/img/developer-advocacy-feedback-loop/06&#43;-&#43;the&#43;advocacy&#43;loop.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ada68f15f833cffb5caa977dfc0e2d56">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/developer-advocacy-feedback-loop/06&#43;-&#43;the&#43;advocacy&#43;loop.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Remember: We&rsquo;re in it together! It&rsquo;s a team&rsquo;s effort. Let&rsquo;s make our users happy! And how to make them happy? By making great products, with great user and developer experience. By showing empathy toward our users, wear their shoes, listen to their feedback, and let that feedback be heard up above, to improve our products, by advocating for our users. That&rsquo;s where the feedback loop closes. Thanks for your attention.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Running Micronaut serverlessly on Google Cloud Platform</title><link>https://glaforge.dev/posts/2020/08/04/running-micronaut-serverlessly-on-google-cloud-platform/</link><pubDate>Tue, 04 Aug 2020 18:49:49 +0100</pubDate><guid>https://glaforge.dev/posts/2020/08/04/running-micronaut-serverlessly-on-google-cloud-platform/</guid><description>&lt;p>Last week, I had the pleasure of presenting &lt;a href="https://micronaut.io/">Micronaut&lt;/a> in action on Google Cloud Platform, via a &lt;a href="https://objectcomputing.com/products/micronaut/resources/serverless-micronaut-on-google-cloud">webinar&lt;/a> organized by OCI. Particularly, I focused on the serverless compute options available: &lt;a href="https://cloud.google.com/functions">Cloud Functions&lt;/a>, &lt;a href="http://cloud.google.com/appengine">App Engine&lt;/a>, and &lt;a href="https://cloud.google.com/run">Cloud Run&lt;/a>.&lt;/p>
&lt;p>Here are the slides I presented. However, the real meat is in the demos which are not displayed on this deck! So let&amp;rsquo;s have a closer look at them, until the video is published online.&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="9da7fe86bd4047508effb1ae34af5ed0" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>On Google Cloud Platform, you have three solutions when you want to deploy your code in a serverless fashion (ie. hassle-free infrastructure, automatic scaling, pays-as-you-go): &lt;/p></description><content:encoded>
<![CDATA[<p>Last week, I had the pleasure of presenting <a href="https://micronaut.io/">Micronaut</a> in action on Google Cloud Platform, via a <a href="https://objectcomputing.com/products/micronaut/resources/serverless-micronaut-on-google-cloud">webinar</a> organized by OCI. Particularly, I focused on the serverless compute options available: <a href="https://cloud.google.com/functions">Cloud Functions</a>, <a href="http://cloud.google.com/appengine">App Engine</a>, and <a href="https://cloud.google.com/run">Cloud Run</a>.</p>
<p>Here are the slides I presented. However, the real meat is in the demos which are not displayed on this deck! So let&rsquo;s have a closer look at them, until the video is published online.</p>
<script async class="speakerdeck-embed" data-id="9da7fe86bd4047508effb1ae34af5ed0" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>On Google Cloud Platform, you have three solutions when you want to deploy your code in a serverless fashion (ie. hassle-free infrastructure, automatic scaling, pays-as-you-go): </p>
<ul>
<li>For event-oriented logic that reacts to cloud events (a new file in cloud storage, a change in a database document, a Pub/Sub message) you can go with a function. </li>
<li>For a web frontend, a REST API, a mobile API backend, also for serving static assets for single-page apps, App Engine is going to do wonders. </li>
<li>But you can also decide to containerize your applications and run them as containers on Cloud Run, for all kinds of needs.</li>
</ul>
<p>Both Cloud Functions and App Engine provide a Java 11 runtime (the latest LTS version of Java at the time of writing), but with Cloud Run, in a container, you can of course package whichever Java runtime environment that you want.</p>
<p>And the good news is that you can run Micronaut easily on all those three environments!</p>
<h2 id="micronaut-on-cloud-functions">Micronaut on Cloud Functions</h2>
<h3 id="http-functions">HTTP functions</h3>
<p>Of those three solutions, Cloud Functions is the one that received a special treatment, as the Micronaut team worked on a <a href="https://micronaut-projects.github.io/micronaut-gcp/2.0.x/guide/#cloudFunction">dedicated integration</a> with the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework API</a> for Java. Micronaut supports both types of functions: HTTP and background functions.</p>
<p>For HTTP functions, you can use a plain Micronaut controller. Your usual controllers can be turned into an HTTP function.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.example</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/hello&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(uri<span style="color:#666">=</span><span style="color:#4070a0">&#34;/&#34;</span>,<span style="color:#bbb"> </span>produces<span style="color:#666">=</span><span style="color:#4070a0">&#34;text/plain&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">index</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Micronaut on Cloud Functions&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Micronaut Launch tool even allows you to create a dedicated scaffolded project with the right configuration (ie. the right Micronaut integration JAR, the Gradle configuration, including for running functions locally on your machine.) Pick the application type in the Launch configuration, and add the <code>google-cloud-function</code> module.</p>
<p>In build.gradle, Launch will add the Functions Frameworks&rsquo; invoker dependency, which allows you to run your functions locally on your machine (it&rsquo;s also the framework that is used in the cloud to invoke your functions, ie. the same portable and open source code):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>invoker<span style="color:#666">(</span><span style="color:#4070a0">&#34;com.google.cloud.functions.invoker:java-function-invoker:1.0.0-beta1&#34;</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>It adds the Java API of the Functions Framework, as <code>compileOnly</code> as it&rsquo;s provided by the platform when running in the cloud:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>compileOnly<span style="color:#666">(</span><span style="color:#4070a0">&#34;com.google.cloud.functions:functions-framework-api&#34;</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>And Micronaut&rsquo;s own GCP Functions integration dependency:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>implementation<span style="color:#666">(</span><span style="color:#4070a0">&#34;io.micronaut.gcp:micronaut-gcp-function-http&#34;</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>And there&rsquo;s also a new task called <code>runFunction</code>, which allows you to run your function locally:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew runFunction
</span></span></code></pre></div><p>If you decide to use Maven, the same dependencies are applied to your project, but there&rsquo;s a dedicated Maven plugin that is provided to run functions locally.</p>
<pre tabindex="0"><code>./mvnw function:run
</code></pre><p>Then to deploy your HTTP function, you can learn more about the topic in the <a href="https://cloud.google.com/functions/docs/deploying">documentation</a>. If you deploy with the gcloud command-line SDK, you will deploy with a command similar to the following one (depending on the region, or size of the instance you want to use):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud functions deploy hello <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --region europe-west1 <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --trigger-http --allow-unauthenticated <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --runtime java11 --memory 512MB <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --entry-point io.micronaut.gcp.function.http.HttpFunction
</span></span></code></pre></div><p>Note that Cloud Functions can build your functions from sources when you deploy, or it can deploy a pre-build shadowed JAR (as configured by Launch.)</p>
<h3 id="background-functions">Background functions</h3>
<p>For background functions, in Launch, select the Micronaut serverless function type. Launch will create a class implementing the BackgroundFunction interface from the Function Frameworks APIs. But it will extend the <code>GoogleFunctionInitializer</code> class from Micronaut&rsquo;s function integration, which takes care of all the usual wiring (like dependency injection). This function by default receives a Pub/Sub message, but there are other types of events that you can receive, like when a new file is uploaded in cloud storage, a new or changed document in the Firestore nosql document database, etc.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.example</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.gcp.function.GoogleFunctionInitializer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">javax.inject.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">PubSubFunction</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">extends</span><span style="color:#bbb"> </span>GoogleFunctionInitializer<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>BackgroundFunction<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Inject</span><span style="color:#bbb"> </span>LoggingService<span style="color:#bbb"> </span>loggingService;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">accept</span>(PubSubMessage<span style="color:#bbb"> </span>pubsubMsg,<span style="color:#bbb"> </span>Context<span style="color:#bbb"> </span>context)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#bbb"> </span>textMessage<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>String(Base64.<span style="color:#4070a0">getDecoder</span>().<span style="color:#4070a0">decode</span>(pubsubMsg.<span style="color:#4070a0">data</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>loggingService.<span style="color:#4070a0">logMessage</span>(textMessage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">PubSubMessage</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>data;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Map<span style="color:#bbb"> </span>attributes;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>messageId;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>publishTime;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Singleton</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">LoggingService</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">logMessage</span>(String<span style="color:#bbb"> </span>txtMessage)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(txtMessage);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>When deploying, you&rsquo;ll define a different trigger, for example here, it&rsquo;s a Pub/Sub message, so you&rsquo;ll use a <code>--trigger-topic TOPIC_NAME</code> flag to tell the platform you want to receive messages on that topic.</p>
<p>For deployment, the gcloud command would look as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud functions deploy pubsubFn <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --region europe-west1 <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --trigger-topic TOPIC_NAME <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --runtime java11 --memory 512MB <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --entry-point com.example.PubSubFunction
</span></span></code></pre></div><h2 id="micronaut-on-app-engine">Micronaut on App Engine</h2>
<p>Micronaut deploys fine as well on App Engine. I <a href="https://glaforge.dev/posts/2019/07/04/getting-started-with-micronaut-on-google-app-engine-java-11/">wrote about it</a> in the past already. If you&rsquo;re using Micronaut Launch, just select the Application type. App Engine allows you to deploy the standalone runnable JARs generated by the configured shadow JAR plugin. But if you want to easily stage your application deliverable, to run the application locally, to deploy, you can also use the Gradle App Engine plugin.</p>
<p>For that purpose, you should add the following build script section in <code>build.gradle</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>buildscript <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    repositories <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        mavenCentral<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#39;com.google.cloud.tools:appengine-gradle-plugin:2.3.0&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>And then apply the plugin with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#39;com.google.cloud.tools.appengine&#39;</span>
</span></span></code></pre></div><p>Before packaging the application, there&rsquo;s one extra step you need to go through, which is to add the special App Engine configuration file: app.yaml. You only need to add one line, unless you want to further configure the instance types, specify some JVM flags, point at static assets, etc. But otherwise, you only need this line in <code>src/main/appengine/app.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">runtime</span>:<span style="color:#bbb"> </span>java11<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, stage your application deliverable with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew appengineStage
</span></span></code></pre></div><p>Cd in the directory, and you can deploy with the plugin or with the gcloud SDK:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">cd</span> build/staged-app/
</span></span><span style="display:flex;"><span>gcloud app deploy
</span></span></code></pre></div><p>During the demonstration, I showed a controller that was accessing some data from the <a href="https://cloud.google.com/firestore/">Cloud Firestore</a> nosql database, listing some pet names:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.example</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.util.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.api.core.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.firestore.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.firestore.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">WelcomeController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(uri<span style="color:#666">=</span><span style="color:#4070a0">&#34;/&#34;</span>,<span style="color:#bbb"> </span>produces<span style="color:#666">=</span><span style="color:#4070a0">&#34;text/html&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">index</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Hello Google Cloud!&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(uri<span style="color:#666">=</span><span style="color:#4070a0">&#34;/pets&#34;</span>,<span style="color:#bbb"> </span>produces<span style="color:#666">=</span><span style="color:#4070a0">&#34;application/json&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">pets</span>()<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>Exception<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>StringBuilder<span style="color:#bbb"> </span>petNames<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>StringBuilder().<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;[&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>FirestoreOptions<span style="color:#bbb"> </span>opts<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>FirestoreOptions.<span style="color:#4070a0">getDefaultInstance</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>Firestore<span style="color:#bbb"> </span>db<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>opts.<span style="color:#4070a0">getService</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>ApiFuture<span style="color:#bbb"> </span>query<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>db.<span style="color:#4070a0">collection</span>(<span style="color:#4070a0">&#34;pets&#34;</span>).<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>QuerySnapshot<span style="color:#bbb"> </span>querySnapshot<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>query.<span style="color:#4070a0">get</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>List<span style="color:#bbb"> </span>documents<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>querySnapshot.<span style="color:#4070a0">getDocuments</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">for</span><span style="color:#bbb"> </span>(QueryDocumentSnapshot<span style="color:#bbb"> </span>document<span style="color:#bbb"> </span>:<span style="color:#bbb"> </span>documents)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>petNames.<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;\&#34;&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">append</span>(document.<span style="color:#4070a0">getString</span>(<span style="color:#4070a0">&#34;name&#34;</span>))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>.<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;\&#34;, &#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>petNames.<span style="color:#4070a0">append</span>(<span style="color:#4070a0">&#34;]&#34;</span>).<span style="color:#4070a0">toString</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="micronaut-on-cloud-run">Micronaut on Cloud Run</h2>
<h3 id="building-a-micronaut-container-image-with-jib">Building a Micronaut container image with Jib</h3>
<p>In a previous article, I talked about how to try <a href="https://glaforge.dev/posts/2020/03/24/start-the-fun-with-java-14-and-micronaut-inside-serverless-containers-on-cloud-run/">Micronaut with Java 14 on Google Cloud</a>. I was explaining how to craft your own <code>Dockerfile</code>, instead of the one generated then by default by Micronaut Launch (now, it is using <code>openjdk:14-alpine</code>). But instead of fiddling with Docker, in my demos, I thought it was cleaner to use Jib. <a href="https://github.com/GoogleContainerTools/jib">Jib</a> is a tool to create cleanly layered container images for your Java applications, without requiring a Docker daemon. There are plugins available for Gradle and Maven, I used the Gradle one by configuring my <code>build.gradle</code> with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>plugins <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">...</span>
</span></span><span style="display:flex;"><span>    id <span style="color:#4070a0">&#34;com.google.cloud.tools.jib&#34;</span> version <span style="color:#4070a0">&#34;2.4.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>And by configuring the <code>jib</code> task with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>jib <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    to <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        image <span style="color:#666">=</span> <span style="color:#4070a0">&#34;gcr.io/serverless-micronaut/micronaut-news&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    from <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        image <span style="color:#666">=</span> <span style="color:#4070a0">&#34;openjdk:14-alpine&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>The from/image line defines the base image to use, and the <code>to</code>/<code>image</code> points at the location in Google Cloud Container Registry where the image will be built, and we can then point Cloud Run at this image for deployment:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud config <span style="color:#007020">set</span> run/region europe-west1
</span></span><span style="display:flex;"><span>gcloud config <span style="color:#007020">set</span> run/platform managed
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>./gradlew jib
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>gcloud run deploy news --image gcr.io/serverless-micronaut/micronaut-news --allow-unauthenticated
</span></span></code></pre></div><h2 id="bonus-points-server-sent-events">Bonus points: Server-Sent Events</h2>
<p>In the demo, I showed the usage of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Sent Events</a>. Neither Cloud Functions nor App Engine support any kind of streaming, as there&rsquo;s a global frontend server in the Google Cloud infrastructure that buffers requests and responses. But Cloud Run supports streaming (HTTP/2 streaming, gRPC streaming, server-sent events, and WebSocket streaming).</p>
<p>So that was a great excuse to play with Micronaut&rsquo;s SSE support. I went with a slightly modified example from the documentation, to emit a few string messages a second apart:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.example</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.MediaType</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.sse.Event</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.scheduling.TaskExecutors</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.scheduling.annotation.ExecuteOn</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.reactivex.Flowable</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">org.reactivestreams.Publisher</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/news&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">NewsController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@ExecuteOn</span>(TaskExecutors.<span style="color:#4070a0">IO</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(produces<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>MediaType.<span style="color:#4070a0">TEXT_EVENT_STREAM</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Publisher<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">index</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>String<span style="color:#666">[]</span><span style="color:#bbb"> </span>ids<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>String<span style="color:#666">[]</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;1&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;2&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;3&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;4&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;5&#34;</span><span style="color:#bbb"> </span>};<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>Flowable.<span style="color:#4070a0">generate</span>(()<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>0,<span style="color:#bbb"> </span>(i,<span style="color:#bbb"> </span>emitter)<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">if</span><span style="color:#bbb"> </span>(i<span style="color:#bbb"> </span><span style="color:#666">&lt;</span><span style="color:#bbb"> </span>ids.<span style="color:#4070a0">length</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>emitter.<span style="color:#4070a0">onNext</span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                    </span>Event.<span style="color:#4070a0">of</span>(<span style="color:#4070a0">&#34;Event #&#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>i)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>{<span style="color:#bbb"> </span>Thread.<span style="color:#4070a0">sleep</span>(1000);<span style="color:#bbb"> </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">catch</span><span style="color:#bbb"> </span>(Throwable<span style="color:#bbb"> </span>t)<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">else</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>emitter.<span style="color:#4070a0">onComplete</span>();<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#666">++</span>i;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then I accessed the /news controller and was happy to see that the response was not buffered and that the events were showing up every second.</p>
<p>Apart from getting on board of this alpha feature of Cloud Run (via the form mentioned to get my GCP project whitelisted), I didn&rsquo;t have to do anything special to my Micronaut setup from the previous section. No further configuration required, it just worked out of the box.</p>
<h2 id="summary">Summary</h2>
<p>The great benefit to using Micronaut on Google Cloud Platform&rsquo;s serverless solutions is that thanks to Micronaut&rsquo;s ahead-of-time compilation techniques, it starts and runs super fast, and consumes much less memory than other Java frameworks. Further down the road, you can also take advantage of GraalVM for even faster startup and lower memory usage. Although my examples were in Java, you can also use Kotlin or Groovy if you prefer.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Video: Getting started with Java on Google Cloud Functions</title><link>https://glaforge.dev/talks/2020/07/28/video-getting-started-with-java-on-google-cloud-functions/</link><pubDate>Tue, 28 Jul 2020 13:01:28 +0100</pubDate><guid>https://glaforge.dev/talks/2020/07/28/video-getting-started-with-java-on-google-cloud-functions/</guid><description>&lt;p>For the 24 hours of talks by Google Cloud DevRel, I recorded my talk about the new Java 11 runtime for Google Cloud Functions.
I wrote about this runtime in this &lt;a href="http://glaforge.appspot.com/article/deploying-serverless-functions-in-groovy-on-the-new-java-11-runtime-for-google-cloud-functions">article&lt;/a> 
showing for example how to run &lt;a href="https://groovy-lang.org/">Apache Groovy&lt;/a> functions, and I also wrote about it on the 
&lt;a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">GCP blog&lt;/a> 
and &lt;a href="https://developers.googleblog.com/2020/05/java-11-for-cloud-functions.html">Google Developers blog&lt;/a> as well.&lt;/p>
&lt;p>In this video, I&amp;rsquo;m giving a quick explanations on the serverless approach, the various serverless options provided by Google Cloud,
and then I dive into the various shapes Java functions can take (HTTP and background functions),
the interfaces you have to implement when authoring a function.
And I also do various demonstrations, deploying Java functions, Groovy functions, or Micronaut functions!&lt;/p></description><content:encoded>
<![CDATA[<p>For the 24 hours of talks by Google Cloud DevRel, I recorded my talk about the new Java 11 runtime for Google Cloud Functions.
I wrote about this runtime in this <a href="http://glaforge.appspot.com/article/deploying-serverless-functions-in-groovy-on-the-new-java-11-runtime-for-google-cloud-functions">article</a> 
showing for example how to run <a href="https://groovy-lang.org/">Apache Groovy</a> functions, and I also wrote about it on the 
<a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">GCP blog</a> 
and <a href="https://developers.googleblog.com/2020/05/java-11-for-cloud-functions.html">Google Developers blog</a> as well.</p>
<p>In this video, I&rsquo;m giving a quick explanations on the serverless approach, the various serverless options provided by Google Cloud,
and then I dive into the various shapes Java functions can take (HTTP and background functions),
the interfaces you have to implement when authoring a function.
And I also do various demonstrations, deploying Java functions, Groovy functions, or Micronaut functions!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/d7eYFI-jbcc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Introducing Java 11 on Google Cloud Functions</title><link>https://glaforge.dev/posts/2020/05/27/introducing-java-11-on-google-cloud-functions/</link><pubDate>Wed, 27 May 2020 11:47:45 +0100</pubDate><guid>https://glaforge.dev/posts/2020/05/27/introducing-java-11-on-google-cloud-functions/</guid><description>&lt;p>The Java programming language recently turned 25 years old, and it&amp;rsquo;s still one of the top-used languages powering today&amp;rsquo;s enterprise application customers. On Google Cloud, you can already run serverless Java microservices in App Engine and Cloud Run. Today we&amp;rsquo;re bringing Java 11 to Google Cloud Functions, an event-driven serverless compute platform that lets you run locally or in the cloud without having to provision servers. That means you can now write Cloud Functions using your favorite JVM languages (&lt;a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/helloworld">Java&lt;/a>, &lt;a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/kotlin-helloworld">Kotlin&lt;/a>, &lt;a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/groovy-helloworld">Groovy&lt;/a>, &lt;a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/scala-helloworld">Scala&lt;/a>, etc) with our &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework for Java&lt;/a>, and also with &lt;a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-function/3.0.6.RELEASE/reference/html/gcp.html">Spring Cloud Functions&lt;/a> and &lt;a href="https://micronaut-projects.github.io/micronaut-gcp/2.0.x/guide/#simpleFunctions">Micronaut&lt;/a>!&lt;/p></description><content:encoded>
<![CDATA[<p>The Java programming language recently turned 25 years old, and it&rsquo;s still one of the top-used languages powering today&rsquo;s enterprise application customers. On Google Cloud, you can already run serverless Java microservices in App Engine and Cloud Run. Today we&rsquo;re bringing Java 11 to Google Cloud Functions, an event-driven serverless compute platform that lets you run locally or in the cloud without having to provision servers. That means you can now write Cloud Functions using your favorite JVM languages (<a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/helloworld">Java</a>, <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/kotlin-helloworld">Kotlin</a>, <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/groovy-helloworld">Groovy</a>, <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/scala-helloworld">Scala</a>, etc) with our <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework for Java</a>, and also with <a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-function/3.0.6.RELEASE/reference/html/gcp.html">Spring Cloud Functions</a> and <a href="https://micronaut-projects.github.io/micronaut-gcp/2.0.x/guide/#simpleFunctions">Micronaut</a>!</p>
<p>With Cloud Functions for Java 11, now in beta, you can use Java to build business-critical applications and integration layers, and deploy the function in a fully managed environment, complete with access to resources in a private VPC network. Java functions will scale automatically based on your load. You can write <a href="https://cloud.google.com/functions/docs/writing/http">HTTP functions</a> to respond to HTTP events, and <a href="https://cloud.google.com/functions/docs/writing/background">background functions</a> to process events sourced from various cloud and GCP services, such as Pub/Sub, Cloud Storage, Firestore, and more.</p>
<p><figure>
  <a href="#img-d036b25bbb3757b4928c8057371f001b">
    <img src="/img/j11gcf/Cloud_Functions__Product_Strategy-01.jpg"
      alt="/img/j11gcf/Cloud_Functions__Product_Strategy-01.jpg"
       />
  </a>
  <figcaption>/img/j11gcf/Cloud_Functions__Product_Strategy-01.jpg</figcaption>
</figure>
<div class="lightbox" id="img-d036b25bbb3757b4928c8057371f001b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/j11gcf/Cloud_Functions__Product_Strategy-01.jpg"
    alt="/img/j11gcf/Cloud_Functions__Product_Strategy-01.jpg"
     />
  <div class="lightbox-caption">/img/j11gcf/Cloud_Functions__Product_Strategy-01.jpg</div>
</div>
</p>
<p>Functions are a great fit for serverless application backends for integrating with third-party services and APIs, or for mobile or IoT backends. You can also use functions for real-time data processing systems, like processing files as they are uploaded to Cloud Storage, or to handle real-time streams of events from Pub/Sub. Last but not least, functions can serve intelligent applications like virtual assistants and chat bots, or video, image and sentiment analysis.</p>
<h2 id="cloud-functions-for-java-11-example">Cloud Functions for Java 11 example</h2>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/UsYRKkibLPI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>You can develop functions using the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java/">Functions Framework for Java</a>, an open source functions-as-a-service framework for writing portable Java functions. You can develop and run your functions locally, deploy them to Cloud Functions, or to another Java environment.</p>
<p>An HTTP function simply implements the <a href="https://javadoc.io/static/com.google.cloud.functions/functions-framework-api/1.0.1/com/google/cloud/functions/HttpFunction.html"><code>HttpFunction</code></a> interface:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloWorld</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>HttpFunction<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">service</span>(HttpRequest<span style="color:#bbb"> </span>request,<span style="color:#bbb"> </span>HttpResponse<span style="color:#bbb"> </span>response)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>IOException<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>writer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">getWriter</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>writer.<span style="color:#4070a0">write</span>(<span style="color:#4070a0">&#34;Hello world!&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Add the Functions Framework API dependency to the Maven pom.xml:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud.functions<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>functions-framework-api<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.0.1<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;scope&gt;</span>provided<span style="color:#062873;font-weight:bold">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>Then add the the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java#running-a-function-with-the-maven-plugin">Function Maven plugin</a> so you can run the function locally:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;plugin&gt;</span>
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud.functions<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>function-maven-plugin<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.9.2<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&lt;configuration&gt;</span>
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&lt;functionTarget&gt;</span>function.HelloWorld<span style="color:#062873;font-weight:bold">&lt;/functionTarget&gt;</span>
</span></span><span style="display:flex;"><span> <span style="color:#062873;font-weight:bold">&lt;/configuration&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/plugin&gt;</span>
</span></span></code></pre></div><p>Run the function locally:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mvn <span style="color:#007020;font-weight:bold">function</span>:run
</span></span></code></pre></div><p>You can also use your IDE to launch this Maven target in Debugger mode to debug the function locally.</p>
<p><figure>
  <a href="#img-ef0c6676edfcadf6a0281ffd9957c032">
    <img src="/img/j11gcf/2_Cloud_Functions_for_Java_11.max-900x900.png"
      alt="/img/j11gcf/2_Cloud_Functions_for_Java_11.max-900x900.png"
       />
  </a>
  <figcaption>/img/j11gcf/2_Cloud_Functions_for_Java_11.max-900x900.png</figcaption>
</figure>
<div class="lightbox" id="img-ef0c6676edfcadf6a0281ffd9957c032">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/j11gcf/2_Cloud_Functions_for_Java_11.max-900x900.png"
    alt="/img/j11gcf/2_Cloud_Functions_for_Java_11.max-900x900.png"
     />
  <div class="lightbox-caption">/img/j11gcf/2_Cloud_Functions_for_Java_11.max-900x900.png</div>
</div>
</p>
<p>To deploy the function, you can use the gcloud command line:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud beta functions deploy helloworld-function <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span> --entry-point <span style="color:#007020;font-weight:bold">function</span>.HelloWorld --runtime java11 --trigger-http
</span></span></code></pre></div><p>Alternatively, you can also deploy with the Function Maven plugin:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mvn <span style="color:#007020;font-weight:bold">function</span>:deploy -Dfunction.deploy.name<span style="color:#666">=</span>helloworld-function
</span></span></code></pre></div><p>You can find the <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/helloworld">full example on GitHub</a>. In addition to running this function in the fully managed Cloud Functions environment, you can also <a href="https://github.com/GoogleCloudPlatform/functions-framework-java#running-the-functions-framework-directly">bring the Functions Framework runtime</a> with you to other environments, such as Cloud Run, Google Kubernetes Engine, or a virtual machine. In addition, Java 8 users can now take advantage of Java 11 features. The majority of the use cases of the Java 8 programming model are supported in Java 11.</p>
<h2 id="third-party-framework-support">Third-party framework support</h2>
<p>In addition to our Functions Framework for Java, both the <a href="https://micronaut-projects.github.io/micronaut-gcp/2.0.x/guide/#simpleFunctions">Micronaut </a>framework and the <a href="https://spring.io/projects/spring-cloud-function">Spring Cloud Function</a> project now have out-of-the-box support for Google Cloud Functions. You can create both an HTTP function and background function using the respective framework&rsquo;s programming model, including capabilities like dependency injection.</p>
<h3 id="micronaut">Micronaut</h3>
<p>The Micronaut team implemented dedicated support for the Cloud Functions Java 11 runtime. Instead of implementing Functions Framework&rsquo;s HttpFunction interface directly, you can use Micronaut&rsquo;s programming model, such that a Helloworld HTTP Function can simply be a <a href="https://docs.micronaut.io/2.0.0.M2/guide/index.html#creatingServer">Micronaut controller</a>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/hello&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(<span style="color:#4070a0">&#34;/{name}&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span><span style="color:#06287e">greet</span>(String<span style="color:#bbb"> </span>name)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Hello &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>name;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You can find a <a href="https://github.com/micronaut-projects/micronaut-gcp/tree/master/examples/hello-world-cloud-function">full example of Micronaut with Cloud Functions</a> and its <a href="https://micronaut-projects.github.io/micronaut-gcp/snapshot/guide/#httpFunctions">documentation</a> on GitHub.</p>
<h3 id="spring-cloud-functions">Spring Cloud Functions</h3>
<p>The Google Cloud Java Frameworks team worked with the Spring team to bring <a href="https://spring.io/projects/spring-cloud-gcp">Spring Cloud GCP</a> project to help Spring Boot users easily leverage Google Cloud services. More recently, the team worked with the Spring Cloud Function team to bring you <a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-function/3.0.7.RELEASE/reference/html/gcp.html">Spring Cloud Function GCP Adapter</a>. A function can just be a vanilla Java function, so you can run a Spring Cloud Function application on Cloud Functions without having to modify your code to run on Google Cloud.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Bean</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>Function<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span><span style="color:#06287e">uppercase</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>value<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>value.<span style="color:#4070a0">toUpperCase</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>You can find a full example of a <a href="https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-gcp-http">Spring Cloud Function with Cloud Functions</a> on GitHub.</p>
<h2 id="jvm-languages">JVM Languages</h2>
<p>In addition to using the <a href="https://advancedweb.hu/new-language-features-since-java-8-to-14/">latest Java 11 language features</a> with Cloud Functions, you can also use your favorite JVM languages, such as Kotlin, Groovy, and Scala, and more. For example, here&rsquo;s a function written with Kotlin:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloWorld</span> : HttpFunction {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">fun</span> <span style="color:#06287e">helloWorld</span>(req: HttpRequest, res: HttpResponse) {
</span></span><span style="display:flex;"><span>        with(res.writer) {
</span></span><span style="display:flex;"><span>            write(<span style="color:#4070a0">&#34;Hello Kotlin World!&#34;</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here&rsquo;s the same function with Groovy:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloWorld</span> <span style="color:#007020;font-weight:bold">implements</span> HttpFunction <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#902000">void</span> <span style="color:#06287e">service</span><span style="color:#666">(</span>HttpRequest req<span style="color:#666">,</span> HttpResponse res<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        res<span style="color:#666">.</span><span style="color:#4070a0">writer</span><span style="color:#666">.</span><span style="color:#4070a0">write</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;Hello Groovy World!&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>You can take a <a href="http://glaforge.appspot.com/article/deploying-serverless-functions-in-groovy-on-the-new-java-11-runtime-for-google-cloud-functions">deeper dive into a Groovy example</a>, and otherwise, find all the examples on GitHub (<a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/kotlin-helloworld">Kotlin</a>, <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/groovy-helloworld">Groovy</a>, <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/functions/helloworld/scala-helloworld">Scala</a>).</p>
<h2 id="try-cloud-functions-for-java-11-today">Try Cloud Functions for Java 11 today</h2>
<p>Cloud Functions for Java 11 is now in beta, so you can try it today with your favorite JVM language and frameworks. Read the <a href="https://cloud.google.com/functions/docs/quickstart-java">Quick Start guide</a>, learn how to <a href="https://cloud.google.com/functions/docs/first-java">write your first functions</a>, and try it out with a Google Cloud Platform <a href="https://cloud.google.com/free">free trial</a>. If you want to dive a little bit deeper into the technical aspects, you can also read this <a href="https://developers.googleblog.com/2020/05/java-11-for-cloud-functions.html">article on Google Developers blog</a>. If you&rsquo;re interested in the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">open-source Functions Framework</a> for Java, please don&rsquo;t hesitate to have a look at the project and potentially even contribute to it. We&rsquo;re looking forward to seeing all the Java the functions you write! </p>
<hr />
<p><em>Special thanks to Googlers Éamonn McManus, Magda Zakrzewska‎, Sławek Walkowski, Ludovic Champenois, Katie McCormick, Grant Timmerman, Ace Nassri, Averi Kitsch, Les Vogel, Kurtis Van Gent, Ronald Laeremans, Mike Eltsufin, Dmitry Solomakha, Daniel Zou, Jason Polites, Stewart Reichling, Michael Skura, Karol Farbiś, and Vinod Ramachandran. We also want to thank <a href="https://micronaut.io/">Micronaut</a> and <a href="https://spring.io/projects/spring-cloud-function">Spring Cloud Function</a> teams for working on the Cloud Functions support!</em></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Sip a Cup of Java 11 for Your Cloud Functions</title><link>https://glaforge.dev/posts/2020/05/26/sip-a-cup-of-java-11-for-your-cloud-functions/</link><pubDate>Tue, 26 May 2020 17:17:34 +0100</pubDate><guid>https://glaforge.dev/posts/2020/05/26/sip-a-cup-of-java-11-for-your-cloud-functions/</guid><description>&lt;p>With the &lt;a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">beta of the new Java 11 runtime&lt;/a> for Google &lt;a href="https://cloud.google.com/functions">Cloud Functions&lt;/a>, Java developers can now write their functions using the Java programming language (a language often used in enterprises) in addition to Node.js, Go, or Python. Cloud Functions allow you to run bits of code locally or in the cloud, without provisioning or managing servers: Deploy your code, and let the platform handle scaling up and down for you. Just focus on your code: handle incoming HTTP requests or respond to some cloud events, like messages coming from Cloud Pub/Sub or new files uploaded in Cloud Storage buckets.&lt;/p></description><content:encoded>
<![CDATA[<p>With the <a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">beta of the new Java 11 runtime</a> for Google <a href="https://cloud.google.com/functions">Cloud Functions</a>, Java developers can now write their functions using the Java programming language (a language often used in enterprises) in addition to Node.js, Go, or Python. Cloud Functions allow you to run bits of code locally or in the cloud, without provisioning or managing servers: Deploy your code, and let the platform handle scaling up and down for you. Just focus on your code: handle incoming HTTP requests or respond to some cloud events, like messages coming from Cloud Pub/Sub or new files uploaded in Cloud Storage buckets.</p>
<p>In this article, let&rsquo;s focus on what functions look like, how you can write portable functions, how to run and debug them locally or deploy them in the cloud or on-premises, thanks to the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework</a>, an open source library that runs your functions. But you will also learn about third-party frameworks that you might be familiar with, that also let you create functions using common programming paradigms.</p>
<h2 id="the-shape-of-your-functions">The shape of your functions</h2>
<p>There are two types of functions: <a href="https://cloud.google.com/functions/docs/writing/http">HTTP functions</a>, and <a href="https://cloud.google.com/functions/docs/writing/background">background functions</a>. HTTP functions respond to incoming HTTP requests, whereas background functions react to cloud-related events.</p>
<p>The Java Functions Framework provides an <a href="https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/index.html">API</a> that you can use to author your functions, as well as an invoker which can be called to run your functions locally on your machine, or anywhere with a Java 11 environment.</p>
<p>To get started with this API, you will need to add a dependency in your build files. If you use Maven, add the following dependency tag in <code>pom.xml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud.functions<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>functions-framework-api<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.0.1<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;scope&gt;</span>provided<span style="color:#062873;font-weight:bold">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>If you are using Gradle, add this dependency declaration in <code>build.gradle</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>compileOnly<span style="color:#666">(</span><span style="color:#4070a0">&#34;com.google.cloud.functions:functions-framework-api&#34;</span><span style="color:#666">)</span>
</span></span></code></pre></div><h3 id="responding-to-http-requests">Responding to HTTP requests</h3>
<p>A Java function that receives an incoming HTTP request implements the <code>[HttpFunction](https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/com/google/cloud/functions/HttpFunction.html)</code> interface:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.io.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">Example</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>HttpFunction<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">service</span>(HttpRequest<span style="color:#bbb"> </span>request,<span style="color:#bbb"> </span>HttpResponse<span style="color:#bbb"> </span>response)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>IOException<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>writer<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>response.<span style="color:#4070a0">getWriter</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>writer.<span style="color:#4070a0">write</span>(<span style="color:#4070a0">&#34;Hello developers!&#34;</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>service()</code> method provides an <code>[HttpRequest](https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/com/google/cloud/functions/HttpRequest.html)</code> and an <code>[HttpResponse](https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/com/google/cloud/functions/HttpResponse.html)</code> object. From the request, you can get information about the HTTP headers, the payload body, or the request parameters. It&rsquo;s also possible to handle multipart requests. With the response, you can set a status code or headers, define a body payload and a content-type.</p>
<h2 id="responding-to-cloud-events">Responding to cloud events</h2>
<p>Background functions respond to events coming from the cloud, like new <a href="https://cloud.google.com/pubsub">Pub/Sub</a> messages, <a href="https://cloud.google.com/storage">Cloud Storage</a> file updates, or new or updated data in <a href="https://cloud.google.com/firestore">Cloud Firestore</a>. There are actually two ways to implement such functions, either by dealing with the JSON payloads representing those events, or by taking advantage of object marshalling thanks to the <a href="https://github.com/google/gson">Gson</a> library, which takes care of the parsing transparently for the developer.</p>
<p>With a <a href="https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/com/google/cloud/functions/RawBackgroundFunction.html">RawBackgroundFunction</a>, the responsibility is on you to handle the incoming cloud event JSON-encoded payload. You receive a JSON string, so you are free to parse it however you like, with your JSON parser of your choice:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.Context</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.RawBackgroundFunction</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">RawFunction</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>RawBackgroundFunction<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">accept</span>(String<span style="color:#bbb"> </span>json,<span style="color:#bbb"> </span>Context<span style="color:#bbb"> </span>context)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>...<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>But you also have the option to write a <a href="https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/com/google/cloud/functions/BackgroundFunction.html">BackgroundFunction</a> which uses Gson for unmarshalling a JSON representation into a Java class (a POJO, Plain-Old-Java-Object) representing that payload. To that end, you have to provide the POJO as a generic argument:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.Context</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.BackgroundFunction</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">PubSubFunction</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">implements</span><span style="color:#bbb"> </span>BackgroundFunction<span style="color:#666">&lt;</span>PubSubMsg<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Override</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">accept</span>(PubSubMsg<span style="color:#bbb"> </span>msg,<span style="color:#bbb"> </span>Context<span style="color:#bbb"> </span>context)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>System.<span style="color:#4070a0">out</span>.<span style="color:#4070a0">println</span>(<span style="color:#4070a0">&#34;Received message ID: &#34;</span><span style="color:#bbb"> </span><span style="color:#666">+</span><span style="color:#bbb"> </span>msg.<span style="color:#4070a0">messageId</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">PubSubMsg</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>data;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>Map<span style="color:#666">&lt;</span>String,<span style="color:#bbb"> </span>String<span style="color:#666">&gt;</span><span style="color:#bbb"> </span>attributes;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>messageId;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>String<span style="color:#bbb"> </span>publishTime;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <a href="https://javadoc.io/doc/com.google.cloud.functions/functions-framework-api/latest/com/google/cloud/functions/Context.html">Context</a> parameter contains various metadata fields like timestamps, the type of events, and other attributes.</p>
<p>Which type of background function should you use? It depends on the control you need to have on the incoming payload, or if the Gson unmarshalling doesn&rsquo;t fully fit your needs. But having the unmarshalling covered by the framework definitely streamlines the writing of your function.</p>
<h2 id="running-your-function-locally">Running your function locally</h2>
<p>Coding is always great, but seeing your code actually running is even more rewarding. The Functions Framework comes with the API we used above, but also with an invoker tool that you can use to run functions locally. For improving developer productivity, having a direct and local feedback loop on your own computer makes it much more comfortable than deploying in the cloud for each change you make to your code.</p>
<h3 id="with-maven">With Maven</h3>
<p>If you&rsquo;re building your functions with Maven, you can install the Function Maven plugin in your <code>pom.xml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;plugin&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud.functions<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>function-maven-plugin<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.9.2<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;configuration&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;functionTarget&gt;</span>com.example.Example<span style="color:#062873;font-weight:bold">&lt;/functionTarget&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/configuration&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/plugin&gt;</span>
</span></span></code></pre></div><p>On the command-line, you can then run:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ mvn <span style="color:#007020;font-weight:bold">function</span>:run
</span></span></code></pre></div><p>You can pass extra parameters like <code>--target</code> to define a different function to run (in case your project contains several functions), <code>--port</code> to specify the port to listen to, or <code>--classpath</code> to explicitly set the classpath needed by the function to run. These are the parameters of the underlying <code>[Invoker](https://github.com/GoogleCloudPlatform/functions-framework-java/blob/master/invoker/core/src/main/java/com/google/cloud/functions/invoker/runner/Invoker.java)</code> class. However, to set these parameters via the Maven plugin, you&rsquo;ll have to pass properties with <code>-Drun.functionTarget=com.example.Example</code> and <code>-Drun.port</code>.</p>
<h3 id="with-gradle">With Gradle</h3>
<p>With Gradle, there is no dedicated plugin, but it&rsquo;s easy to configure <code>build.gradle</code> to let you run functions.</p>
<p>First, define a dedicated configuration for the invoker:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>configurations <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    invoker
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>In the dependencies, add the Invoker library:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    invoker <span style="color:#4070a0">&#39;com.google.cloud.functions.invoker:java-function-invoker:1.0.0-beta1&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>And then, create a new task to run the Invoker:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>tasks<span style="color:#666">.</span><span style="color:#4070a0">register</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;runFunction&#34;</span><span style="color:#666">,</span> JavaExec<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    main <span style="color:#666">=</span> <span style="color:#4070a0">&#39;com.google.cloud.functions.invoker.runner.Invoker&#39;</span>
</span></span><span style="display:flex;"><span>    classpath<span style="color:#666">(</span>configurations<span style="color:#666">.</span><span style="color:#4070a0">invoker</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    inputs<span style="color:#666">.</span><span style="color:#4070a0">files</span><span style="color:#666">(</span>configurations<span style="color:#666">.</span><span style="color:#4070a0">runtimeClasspath</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>                 sourceSets<span style="color:#666">.</span><span style="color:#4070a0">main</span><span style="color:#666">.</span><span style="color:#4070a0">output</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    args<span style="color:#666">(</span><span style="color:#4070a0">&#39;--target&#39;</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>         project<span style="color:#666">.</span><span style="color:#4070a0">findProperty</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;runFunction.target&#39;</span><span style="color:#666">)</span> <span style="color:#666">?:</span>
</span></span><span style="display:flex;"><span>         <span style="color:#4070a0">&#39;com.example.Example&#39;</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>         <span style="color:#4070a0">&#39;--port&#39;</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>         project<span style="color:#666">.</span><span style="color:#4070a0">findProperty</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;runFunction.port&#39;</span><span style="color:#666">)</span> <span style="color:#666">?:</span> <span style="color:#40a070">8080</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    doFirst <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        args<span style="color:#666">(</span><span style="color:#4070a0">&#39;--classpath&#39;</span><span style="color:#666">,</span> files<span style="color:#666">(</span>configurations<span style="color:#666">.</span><span style="color:#4070a0">runtimeClasspath</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>                                  sourceSets<span style="color:#666">.</span><span style="color:#4070a0">main</span><span style="color:#666">.</span><span style="color:#4070a0">output</span><span style="color:#666">).</span><span style="color:#4070a0">asPath</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>By default, the above launches the function <code>com.example.Example</code> on port 8080, but you can override those on the command-line, when running gradle or the gradle wrapper:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gradle runFunction -PrunFunction.target<span style="color:#666">=</span>com.example.HelloWorld<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>                     -PrunFunction.port<span style="color:#666">=</span><span style="color:#40a070">8080</span>
</span></span></code></pre></div><h3 id="running-elsewhere-making-your-functions-portable">Running elsewhere, making your functions portable</h3>
<p>What&rsquo;s interesting about the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework</a> is that you are not tied to the Cloud Functions platform for deploying your functions. As long as, in your target environment, you can run your functions with the Invoker class, you can run your functions on <a href="https://cloud.google.com/run">Cloud Run</a>, on <a href="https://cloud.google.com/kubernetes-engine">Google Kubernetes Engine</a>, on <a href="https://cloud.google.com/knative/">Knative</a> environments, on other clouds when you can run Java, or more generally on any servers on-premises. It makes your functions highly portable between environments. But let&rsquo;s have a closer look at deployment now.</p>
<h2 id="deploying-your-functions">Deploying your functions</h2>
<p>You can <a href="https://github.com/GoogleCloudPlatform/functions-framework-java/blob/master/invoker/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java">deploy</a> functions with the Maven plugin as well, with various parameters to tweak for defining regions, memory size, etc. But here, we&rsquo;ll focus on using the <a href="https://cloud.google.com/sdk">cloud SDK</a>, with its <code>gcloud</code> command-line, to deploy our functions.</p>
<p>For example, to deploy an HTTP function, you would type:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud functions deploy exampleFn<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --region europe-west1<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --trigger-http<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --allow-unauthenticated<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --runtime java11<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --entry-point com.example.Example<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --memory 512MB
</span></span></code></pre></div><p>For a background function that would be notified of new messages on a Pub/Sub topic, you would launch:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud functions deploy exampleFn<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --region europe-west1<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --trigger-topic msg-topic<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --runtime java11<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --entry-point com.example.PubSubFunction<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --memory 512MB
</span></span></code></pre></div><p>Note that deployments come in two flavors as well, although the above commands are the same: functions are deployed from source with a <code>pom.xml</code> and built in Google Cloud, but when using a build tool other than Maven, you can also use the same command to deploy a pre-compiled JAR that contains your function implementation. Of course, you&rsquo;ll have to create that JAR first.</p>
<h2 id="what-about-other-languages-and-frameworks">What about other languages and frameworks?</h2>
<p>So far, we looked at Java and the plain <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework</a>, but you can definitely use alternative JVM languages such as <a href="http://groovy-lang.org/">Apache Groovy</a>, <a href="https://kotlinlang.org/">Kotlin</a>, or <a href="https://www.scala-lang.org/">Scala</a>, and third-party frameworks that integrate with Cloud Functions like <a href="https://micronaut.io/">Micronaut</a> and <a href="https://spring.io/projects/spring-boot">Spring Boot</a>!</p>
<h3 id="pretty-groovy-functions">Pretty Groovy functions</h3>
<p>Without covering all those combinations, let&rsquo;s have a look at two examples. What would an HTTP function look like in Groovy?</p>
<p>The first step will be to add Apache Groovy as a dependency in your <code>pom.xml</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.codehaus.groovy<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>groovy-all<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>3.0.4<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;type&gt;</span>pom<span style="color:#062873;font-weight:bold">&lt;/type&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>You will also need the GMaven compiler plugin to compile the Groovy code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;plugin&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.codehaus.gmavenplus<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>gmavenplus-plugin<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.9.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;executions&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;execution&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;goals&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&lt;goal&gt;</span>addSources<span style="color:#062873;font-weight:bold">&lt;/goal&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&lt;goal&gt;</span>addTestSources<span style="color:#062873;font-weight:bold">&lt;/goal&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&lt;goal&gt;</span>compile<span style="color:#062873;font-weight:bold">&lt;/goal&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&lt;goal&gt;</span>compileTests<span style="color:#062873;font-weight:bold">&lt;/goal&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;/goals&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/execution&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/executions&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/plugin&gt;</span>
</span></span></code></pre></div><p>When writing the function code, just use Groovy instead of Java:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloWorldFunction</span> <span style="color:#007020;font-weight:bold">implements</span> HttpFunction <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#902000">void</span> <span style="color:#06287e">service</span><span style="color:#666">(</span>HttpRequest request<span style="color:#666">,</span> HttpResponse response<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        response<span style="color:#666">.</span><span style="color:#4070a0">writer</span><span style="color:#666">.</span><span style="color:#4070a0">write</span> <span style="color:#4070a0">&#34;Hello Groovy World!&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>The same explanations regarding running your function locally or deploying it still applies: the Java platform is pretty open to alternative languages too! And the Cloud Functions builder will happily build your Groovy code in the cloud, since Maven lets you compile this code thanks to the Groovy library.</p>
<h3 id="micronaut-functions">Micronaut functions</h3>
<p>Third-party frameworks also offer a dedicated Cloud Functions integration. Let&rsquo;s have a look at Micronaut.</p>
<p><a href="https://micronaut.io/">Micronaut</a> is a <em>&ldquo;modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications&rdquo;</em>, as explained on its website. It supports the notion of serverless functions, web apps and microservices, and has a dedicated integration for Google Cloud Functions.</p>
<p>In addition to being a very efficient framework with super fast startup times (which is important, to avoid long cold starts on serverless services), what&rsquo;s interesting about using Micronaut is that you can use Micronaut&rsquo;s own programming model, including Dependency Injection, annotation-driven bean declaration, etc.</p>
<p>For HTTP functions, you can use the framework&rsquo;s own <code>@Controller</code> / <code>@Get</code> annotations, instead of the Functions Framework&rsquo;s own interfaces. So for example, a Micronaut HTTP function would look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/hello&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(uri<span style="color:#666">=</span><span style="color:#4070a0">&#34;/&#34;</span>,<span style="color:#bbb"> </span>produces<span style="color:#666">=</span><span style="color:#4070a0">&#34;text/plain&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">index</span>()<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Example Response&#34;</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>This is the standard way in Micronaut to define a Web microservice, but it transparently builds upon the Functions Framework to run this service as a Cloud Function. Furthermore, this programming model offered by Micronaut is portable across other environments, since Micronaut runs in many different contexts.</p>
<p>Last but not least, if you are using the <a href="https://micronaut.io/launch/">Micronaut Launch</a> project (hosted on <a href="https://cloud.google.com/run">Cloud Run</a>) which allows you to scaffold new projects easily (from the command-line or from a nice UI), you can opt for adding the <code>google-cloud-function</code> support module, and even choose your favorite language, build tool, or testing framework:</p>
<p><a href="https://3.bp.blogspot.com/-dgW0l2rewn0/Xs1EprFntNI/AAAAAAAAI3Q/XlDOiwKN_98CGp8qGSRJkTv_ETjqynbjgCLcBGAsYHQ/s1600/micronautlaunch.png"><figure>
  <a href="#img-05e491d60c8302b0446a9de48b68b6d6">
    <img src="https://3.bp.blogspot.com/-dgW0l2rewn0/Xs1EprFntNI/AAAAAAAAI3Q/XlDOiwKN_98CGp8qGSRJkTv_ETjqynbjgCLcBGAsYHQ/s1600/micronautlaunch.png"
      alt="Micronaut Launch"
       />
  </a>
  <figcaption>Micronaut Launch</figcaption>
</figure>
<div class="lightbox" id="img-05e491d60c8302b0446a9de48b68b6d6">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://3.bp.blogspot.com/-dgW0l2rewn0/Xs1EprFntNI/AAAAAAAAI3Q/XlDOiwKN_98CGp8qGSRJkTv_ETjqynbjgCLcBGAsYHQ/s1600/micronautlaunch.png"
    alt="Micronaut Launch"
     />
  <div class="lightbox-caption">Micronaut Launch</div>
</div>
</a></p>
<p>Be sure to check out the documentation for the <a href="https://micronaut-projects.github.io/micronaut-gcp/2.0.0.M3/guide/index.html#cloudFunction">Micronaut Cloud Functions support</a>, and <a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-function/3.0.6.RELEASE/reference/html/gcp.html">Spring Cloud Function support</a>.</p>
<h2 id="whats-next">What&rsquo;s next?</h2>
<p>Now it&rsquo;s your turn to try Cloud Functions for Java 11 today, with your favorite JVM language or third-party frameworks. Read the <a href="https://cloud.google.com/functions/docs/quickstart-java">getting started guide</a>, and try this for free with Google Cloud Platform <a href="https://cloud.google.com/free">free trial</a>. Explore Cloud Functions&rsquo; <a href="https://cloud.google.com/functions">features and use cases</a>, take a look at the <a href="https://cloud.google.com/functions/docs/quickstarts">quickstarts</a>, perhaps even contribute to the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">open source Functions Framework</a>. And we&rsquo;re looking forward to seeing what functions you&rsquo;re going to build on this platform!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Deploying serverless functions in Groovy on the new Java 11 runtime for Google Cloud Functions</title><link>https://glaforge.dev/posts/2020/05/26/deploying-serverless-functions-in-groovy-on-the-new-java-11-runtime-for-google-cloud-functions/</link><pubDate>Tue, 26 May 2020 13:05:07 +0100</pubDate><guid>https://glaforge.dev/posts/2020/05/26/deploying-serverless-functions-in-groovy-on-the-new-java-11-runtime-for-google-cloud-functions/</guid><description>&lt;p>Java celebrates its 25th anniversary! 
Earlier this year, the Apache Groovy team released the big 
&lt;a href="https://groovy-lang.org/releasenotes/groovy-3.0.html">3.0 version&lt;/a> of the programming language. 
&lt;a href="https://github.com/groovy/GMavenPlus/releases/tag/1.9.0">GMavenPlus&lt;/a> was published in version 1.9
(the Maven plugin for compiling Groovy code) which works with Java 14.
And today, Google Cloud opens up the beta of the 
&lt;a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">Java 11 runtime for Cloud Functions&lt;/a>.
What about combining them all?&lt;/p>
&lt;p>I&amp;rsquo;ve been working for a bit on the Java 11 runtime for Google Cloud Functions
(that&amp;rsquo;s the Function-as-a-Service platform of Google Cloud, pay-as-you-go, hassle-free / transparent scaling),
and in this article, I&amp;rsquo;d like to highlight that you can also write and deploy functions with alternative JVM languages 
like &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>Java celebrates its 25th anniversary! 
Earlier this year, the Apache Groovy team released the big 
<a href="https://groovy-lang.org/releasenotes/groovy-3.0.html">3.0 version</a> of the programming language. 
<a href="https://github.com/groovy/GMavenPlus/releases/tag/1.9.0">GMavenPlus</a> was published in version 1.9
(the Maven plugin for compiling Groovy code) which works with Java 14.
And today, Google Cloud opens up the beta of the 
<a href="https://cloud.google.com/blog/products/application-development/introducing-java-11-on-google-cloud-functions">Java 11 runtime for Cloud Functions</a>.
What about combining them all?</p>
<p>I&rsquo;ve been working for a bit on the Java 11 runtime for Google Cloud Functions
(that&rsquo;s the Function-as-a-Service platform of Google Cloud, pay-as-you-go, hassle-free / transparent scaling),
and in this article, I&rsquo;d like to highlight that you can also write and deploy functions with alternative JVM languages 
like <a href="http://groovy-lang.org/">Apache Groovy</a>.</p>
<p>So today, you&rsquo;re going to:</p>
<ul>
<li>Write a simple Groovy 3.0 function,</li>
<li>Compile it with Maven 3.6 and the GMavenPlus 1.9 plugin, </li>
<li>Deploy and run the function on the Cloud Functions Java 11 runtime!</li>
</ul>
<p>Note: If you want to try this at (work from?) home, you will need an account on Google Cloud,
you can easily create a <a href="https://cloud.google.com/free">free account</a> 
and benefit from $300 of cloud credits to get started (including also free quotas for many products).
You will also need to create a billing account, but for the purpose of this tutorial,
you should be within the free quota (so your credit card shouldn&rsquo;t be billed).
Then, head over to the <a href="https://console.cloud.google.com/">console.cloud.google.com</a> cloud console to create a new project.
And then navigate to the Cloud Functions section to enable the service for your project.</p>
<p>Let&rsquo;s get started! So what do we need? A pom.xml file, and a Groovy class!</p>
<p>Let&rsquo;s start with the <code>pom.xml</code> file, and what you should add to your build file.
First of all, since I&rsquo;m using Groovy as my function implementation language, I&rsquo;m going to use GMavenPlus for compilation.
So in the build/plugins section, I configure the plugin as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;plugin&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.codehaus.gmavenplus<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>gmavenplus-plugin<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.9.0<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;executions&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&lt;execution&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;id&gt;</span>groovy-compile<span style="color:#062873;font-weight:bold">&lt;/id&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;phase&gt;</span>process-resources<span style="color:#062873;font-weight:bold">&lt;/phase&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;goals&gt;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#062873;font-weight:bold">&lt;goal&gt;</span>addSources<span style="color:#062873;font-weight:bold">&lt;/goal&gt;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#062873;font-weight:bold">&lt;goal&gt;</span>compile<span style="color:#062873;font-weight:bold">&lt;/goal&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;/goals&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&lt;/execution&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/executions&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;dependencies&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.codehaus.groovy<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>groovy-all<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>3.0.4<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;scope&gt;</span>runtime<span style="color:#062873;font-weight:bold">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&lt;type&gt;</span>pom<span style="color:#062873;font-weight:bold">&lt;/type&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/dependencies&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;/plugin&gt;</span>
</span></span></code></pre></div><p>That way, when I do an <code>mvn compile</code>, my Groovy sources are compiled as part of the compilation lifecycle of Maven.</p>
<p>But I&rsquo;m adding a second plugin, the Functions Framework plugin!
That&rsquo;s a Maven plugin to run functions locally on your machine,
before deploying into the cloud, so that you can have a local developer experience that&rsquo;s easy and fast.
The <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework</a> is actually an open source project on Github.
It&rsquo;s a lightweight API to write your functions with, and it&rsquo;s also a function runner / invoker.
What&rsquo;s interesting is that it also means that you are not locked in the Cloud Functions platform,
but you can run your function locally or anywhere else where you can run a JAR file on a JVM! Great portability!</p>
<p>So let&rsquo;s configure the Functions Framework Maven plugin:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;plugin&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud.functions<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>function-maven-plugin<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>0.9.2<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;configuration&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&lt;functionTarget&gt;</span>mypackage.HelloWorldFunction<span style="color:#062873;font-weight:bold">&lt;/functionTarget&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&lt;/configuration&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;/plugin&gt;</span>
</span></span></code></pre></div><p>I specify a configuration flag to point at the function I want to run.
But we&rsquo;ll come back in a moment on how to run this function locally. We need to write it first!</p>
<p>We need two more things in our <code>pom.xml</code>, a dependency on Groovy, but also on the Functions Framework Java API.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>com.google.cloud.functions<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>functions-framework-api<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>1.0.1<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;scope&gt;</span>provided<span style="color:#062873;font-weight:bold">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;groupId&gt;</span>org.codehaus.groovy<span style="color:#062873;font-weight:bold">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;artifactId&gt;</span>groovy-all<span style="color:#062873;font-weight:bold">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;version&gt;</span>3.0.4<span style="color:#062873;font-weight:bold">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&lt;type&gt;</span>pom<span style="color:#062873;font-weight:bold">&lt;/type&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>So you&rsquo;re all set for the build, let&rsquo;s now create our function in <code>src/main/groovy/mypackage/HelloWorldFunction.groovy</code>.</p>
<p>There are two flavors of functions: <a href="https://cloud.google.com/functions/docs/writing/http">HTTP functions</a> and 
<a href="https://cloud.google.com/functions/docs/writing/background">background functions</a>.
The latter react to cloud events like a new file stored in Cloud Storage, a new data update in the Firestore database, etc.
Whereas the former directly exposes a URL that can be invoked via an HTTP call.
That&rsquo;s the one I want to create to write a symbolic <code>&quot;Hello Groovy World&quot;</code> message in your browser window.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span> mypackage
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.cloud.functions.*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloWorldFunction</span> <span style="color:#007020;font-weight:bold">implements</span> HttpFunction <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#902000">void</span> <span style="color:#06287e">service</span><span style="color:#666">(</span>HttpRequest request<span style="color:#666">,</span> HttpResponse response<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        response<span style="color:#666">.</span><span style="color:#4070a0">writer</span><span style="color:#666">.</span><span style="color:#4070a0">write</span> <span style="color:#4070a0">&#34;Hello Groovy World!&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Yes, that&rsquo;s all there is to it!
You implement a Functions Framework interface, and its <code>service()</code> method.
You have a <code>request</code> / <code>response</code> mode (a <code>request</code> and a <code>response</code> parameters are passed to your method).
You can access the writer to write back to the browser or client that invoked the function.</p>
<p>Now it&rsquo;s time to run the function locally to see if it&rsquo;s working. Just type the following command in your terminal:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mvn <span style="color:#007020;font-weight:bold">function</span>:run
</span></span></code></pre></div><p>After a moment, and some build logs further, you should see something like:</p>
<pre tabindex="0"><code>INFO: Serving function...
INFO: Function: mypackage.HelloWorldFunction
INFO: URL: http://localhost:8080/
</code></pre><p>With your browser (or curl), you can browse this local URL, and you will see the hello world message appearing. Yay!</p>
<p>With the Maven plugin, you can also deploy, but you can use the <a href="https://cloud.google.com/sdk">gcloud</a> 
command-line tool to deploy the function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud functions deploy helloFunction<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --region europe-west1<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --trigger-http --allow-unauthenticated<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --runtime java11<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --entry-point mypackage.HelloWorldFunction<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --memory 512MB
</span></span></code></pre></div><p>After a little moment, the function is deployed, and you&rsquo;ll notice you&rsquo;ll have a URL created for your function looking something like this:</p>
<pre tabindex="0"><code>https://europe-west1-myprojectname.cloudfunctions.net/helloFunction
</code></pre><p>The very same function now runs in the cloud!
A pretty Groovy function! This function is portable:
you can invoke it with the Functions Framework invoker, anywhere you can run a JVM.</p>
<p>Going further, I encourage you to have a look at the <a href="https://github.com/GoogleCloudPlatform/functions-framework-java">Functions Framework documentation on
Github</a> to learn more about it.
Here you deployed the function source and the pom.xml file, as the function will be built directly in the cloud.
But it&rsquo;s also possible to compile and create a JAR locally and deploy that instead.
That&rsquo;s interesting for example if you want to use another build tool, like Gradle. And this will be the purpose of another upcoming article!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Video: the Pic-a-Daily serverless workshop</title><link>https://glaforge.dev/talks/2020/05/18/video-the-pic-a-daily-serverless-workshop/</link><pubDate>Mon, 18 May 2020 14:44:33 +0100</pubDate><guid>https://glaforge.dev/talks/2020/05/18/video-the-pic-a-daily-serverless-workshop/</guid><description>&lt;p>With my partner in crime, &lt;a href="https://twitter.com/meteatamel/status/1262316915642576896">Mete Atamel&lt;/a>,
we ran two editions of our &amp;ldquo;Pic-a-Daily&amp;rdquo; serverless workshop. It&amp;rsquo;s an online, hands-on, workshop,
where developers get their hands on the the &lt;a href="https://cloud.google.com/serverless">serverless products&lt;/a> 
provided by &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/functions">Cloud Functions&lt;/a> &amp;mdash; to develop and run functions, small units of logic glue, to react to events of your cloud projects and services&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/appengine">App Engine&lt;/a> &amp;mdash; to deploy web apps, for web frontends, or API backends&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/run">Cloud Run&lt;/a> &amp;mdash; to deploy and scale containerised services&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://codelabs.developers.google.com/serverless-workshop/">&lt;figure>
&lt;a href="#img-49f695532cf3b3364eef7700f42716d6">
&lt;img src="https://glaforge.dev/img/pic-a-daily-workshop/picadaily.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-49f695532cf3b3364eef7700f42716d6">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/pic-a-daily-workshop/picadaily.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/a>&lt;/p></description><content:encoded>
<![CDATA[<p>With my partner in crime, <a href="https://twitter.com/meteatamel/status/1262316915642576896">Mete Atamel</a>,
we ran two editions of our &ldquo;Pic-a-Daily&rdquo; serverless workshop. It&rsquo;s an online, hands-on, workshop,
where developers get their hands on the the <a href="https://cloud.google.com/serverless">serverless products</a> 
provided by <a href="https://cloud.google.com/">Google Cloud Platform</a>:</p>
<ul>
<li><a href="https://cloud.google.com/functions">Cloud Functions</a> &mdash; to develop and run functions, small units of logic glue, to react to events of your cloud projects and services</li>
<li><a href="https://cloud.google.com/appengine">App Engine</a> &mdash; to deploy web apps, for web frontends, or API backends</li>
<li><a href="https://cloud.google.com/run">Cloud Run</a> &mdash; to deploy and scale containerised services</li>
</ul>
<p><a href="https://codelabs.developers.google.com/serverless-workshop/"><figure>
  <a href="#img-49f695532cf3b3364eef7700f42716d6">
    <img src="/img/pic-a-daily-workshop/picadaily.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-49f695532cf3b3364eef7700f42716d6">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pic-a-daily-workshop/picadaily.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<p>The theme of the workshop is to build a simple photosharing application (hence the play on words, with a picture a day) with those serverless products, but along the way, developers also get to use other services like:</p>
<ul>
<li><a href="https://cloud.google.com/pubsub/">Pub/Sub</a> &mdash; as a messaging fabric to let events flow between your services</li>
<li><a href="https://cloud.google.com/firestore">Firestore</a> &mdash; for storing picture metadata in the scalable document database</li>
<li><a href="https://cloud.google.com/storage">Cloud Storage</a> &mdash; to store the image blobs</li>
<li><a href="https://cloud.google.com/scheduler">Cloud Scheduler</a> &mdash; to run a services on a schedule (ie. cron as a service)</li>
<li><a href="https://cloud.google.com/vision/">Cloud Vision API</a> &mdash; a machine learning API to make sense of what&rsquo;s in your pictures</li>
</ul>
<p><a href="https://codelabs.developers.google.com/serverless-workshop/"><figure>
  <a href="#img-cbae5a5a09b54a4d934b58cb299274e9">
    <img src="/img/pic-a-daily-workshop/diagram.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-cbae5a5a09b54a4d934b58cb299274e9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pic-a-daily-workshop/diagram.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<p>The workshop is freely accessible on our codelabs platform: <a href="https://codelabs.developers.google.com/serverless-workshop/">&ldquo;Pic-a-Daily&rdquo; serverless workshop</a>. So you can follow this hands-on workshop on your own, at your own pace. There are 4 codelabs:</p>
<ul>
<li>
<p>The <a href="https://codelabs.developers.google.com/codelabs/cloud-picadaily-lab1/index.html?index=..%2F..serverless-workshop#13">first one</a> lets you build a function that responds to events as new pictures are uploaded into Cloud Storage, invoking the Vision API to understand what is in the picture, and storing some picture metadata information in Firestore.</p>
</li>
<li>
<p>The <a href="https://codelabs.developers.google.com/codelabs/cloud-picadaily-lab2/index.html?index=..%2F..serverless-workshop#0">second lab</a> will use a Cloud Run service which reacts to new files stored in Cloud Storage too, but will create thumbnails of the pictures.</p>
</li>
<li>
<p>A <a href="https://codelabs.developers.google.com/codelabs/cloud-picadaily-lab3/index.html?index=..%2F..serverless-workshop#0">third lab</a> is also taking advantage of Cloud Run to run on a schedule, thanks to Cloud Scheduler. It creates a collage of the most recent pictures.</p>
</li>
<li>
<p>Last but not least, the <a href="https://codelabs.developers.google.com/codelabs/cloud-picadaily-lab4/index.html?index=..%2F..serverless-workshop#0">fourth lab</a> will let you build a web frontend and backend API on Google App Engine.</p>
</li>
</ul>
<p><a href="https://codelabs.developers.google.com/serverless-workshop/"><figure>
  <a href="#img-83c9873aeb95320ff02a4eaa5bc8ab24">
    <img src="/img/pic-a-daily-workshop/codelabs.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-83c9873aeb95320ff02a4eaa5bc8ab24">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pic-a-daily-workshop/codelabs.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<p>We have a dedicated <a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop">Github repository</a> where you can check-out the code of the various functions, apps and containers, and you can have a look at the <a href="https://speakerdeck.com/meteatamel/pic-a-daily-serverless-workshop">slide deck</a> introducing the workshop and the technologies used.</p>
<p>And now, the videos of the first edition are also available on YouTube!</p>
<p>The first part covers Cloud Functions and Cloud Run with the first two labs:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/wEENQouNsGk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>The second part covers Cloud Run and App Engine:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Y9E1fQcPXP0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Machine learning applied music generation with Magenta</title><link>https://glaforge.dev/posts/2020/05/04/machine-learning-applied-music-generation-with-magenta/</link><pubDate>Mon, 04 May 2020 14:54:06 +0100</pubDate><guid>https://glaforge.dev/posts/2020/05/04/machine-learning-applied-music-generation-with-magenta/</guid><description>&lt;p>I missed this talk from Alexandre Dubreuil, when attending Devoxx Belgium 2019, but I had the chance to watch while doing my elliptical bike run, confined at home. It&amp;rsquo;s about applying Machine Learning to music generation, thanks to the &lt;a href="https://magenta.tensorflow.org/">Magenta project&lt;/a>, which is based on &lt;a href="https://www.tensorflow.org/">Tensorflow&lt;/a>.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/O4uBa0KMeNY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>I like playing music (a bit of piano &amp;amp; guitar) once in a while, so as a geek, I&amp;rsquo;ve also always been interested in computer generated music. And it&amp;rsquo;s hard to generate music that actually sounds pleasant to the ear! Alexandre explains that it&amp;rsquo;s hard to encode the rules a computer could follow to play music, but that machine learning is pretty interesting, as it&amp;rsquo;s able to learn complex functions, thus understanding what does sound good.&lt;/p></description><content:encoded>
<![CDATA[<p>I missed this talk from Alexandre Dubreuil, when attending Devoxx Belgium 2019, but I had the chance to watch while doing my elliptical bike run, confined at home. It&rsquo;s about applying Machine Learning to music generation, thanks to the <a href="https://magenta.tensorflow.org/">Magenta project</a>, which is based on <a href="https://www.tensorflow.org/">Tensorflow</a>.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/O4uBa0KMeNY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>I like playing music (a bit of piano &amp; guitar) once in a while, so as a geek, I&rsquo;ve also always been interested in computer generated music. And it&rsquo;s hard to generate music that actually sounds pleasant to the ear! Alexandre explains that it&rsquo;s hard to encode the rules a computer could follow to play music, but that machine learning is pretty interesting, as it&rsquo;s able to learn complex functions, thus understanding what does sound good.</p>
<p>He, then, covers the various types of music representations, like MIDI scores which are quite light in terms of data, and audio waves which on the high end of data as there are thousands of data points representing the position on the wave along the time axis. While MIDI represents a note of music, audio waves really represent the sound physically as a wave (of data points).</p>
<p>Note that in the following part of the article, I&rsquo;m not an ML / AI expert, so I&rsquo;m just trying to explain what I actually understood :-)</p>
<p>For MIDI, Recurrent Neural Networks (RNN) make sense, as they work on sequences for the input and output, and also have the ability to remember past information. And that&rsquo;s great as you find recurring patterns in music (series of chords, main song lines, etc.)</p>
<p>RNN tend to forget progressively those past events, so those networks often use Long-Short-Term-Memory to keep some of their memory fresh.</p>
<p>Variational Auto-Encoders are a pair of networks that diminish the dimensions of outputs compared to the quantity in input, but to then re-expand back to the same size of output. So VAEs try to actually generate back something that&rsquo;s close to what was initially given in input, but it events to reproduce similar patterns.</p>
<p>For audio waves, Magenta comes with a Convolutional Neural Network (CNN) called WaveNet, that&rsquo;s used for example for voice generation on devices like Google Home. There are WaveNet Auto-Encoders that also generate audio waves, because it can learn to generate the actual sound of instruments, or create totally new instruments, or mixes of sounds. Alexandre shows some cool demos of weird instruments made of cat sounds and musical instruments.</p>
<p>Magenta comes with various RNNs for drums, melody, polyphony, performance. With auto-encoders for WaveNet and MIDI too. There&rsquo;s also a Generative Adversarial Network (GAN) for audio waves. GANs are often used for generating things like pictures, for example.</p>
<p>The demos in this presentation are quite cool, with creating new instruments (cat + musical instrument), or for generating sequences of notes (drum score, melody score)</p>
<p>Alexandre ends the presentation with pointers to things like data sets of music, as neural networks further need to learn about style, performance, and networks need plenty of time to learn from existing music and instrument sounds, so as to create something nice to hear! He shows briefly some other cool demos using <a href="https://www.tensorflow.org/js">TensorFlow.js</a>, so that it works in the browser and that you can more easily experiment with music generation.</p>
<p>Also, Alexandre wrote the book &ldquo;<a href="https://www.packtpub.com/eu/data/hands-on-music-generation-with-magenta">Hands-On Music Generation with Magenta</a>&rdquo;, so if you want to dive deeper, there&rsquo;s much to read and experiment with!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>HTML semantic tags</title><link>https://glaforge.dev/posts/2020/04/29/html-semantic-tags/</link><pubDate>Wed, 29 Apr 2020 14:56:35 +0100</pubDate><guid>https://glaforge.dev/posts/2020/04/29/html-semantic-tags/</guid><description>&lt;p>We all know about HTML 5, right? Well, I knew about some of the new semantic tags,
like &lt;code>header&lt;/code> / &lt;code>nav&lt;/code> / &lt;code>main&lt;/code> / &lt;code>article&lt;/code> / &lt;code>aside&lt;/code> / &lt;code>footer&lt;/code>,
but I&amp;rsquo;m still falling down to using tons of divs and spans instead. So as I want to refresh that blog at some point,
it was time I revise those semantic tags. Let&amp;rsquo;s take the little time we have during confinement to learn something!&lt;/p></description><content:encoded>
<![CDATA[<p>We all know about HTML 5, right? Well, I knew about some of the new semantic tags,
like <code>header</code> / <code>nav</code> / <code>main</code> / <code>article</code> / <code>aside</code> / <code>footer</code>,
but I&rsquo;m still falling down to using tons of divs and spans instead. So as I want to refresh that blog at some point,
it was time I revise those semantic tags. Let&rsquo;s take the little time we have during confinement to learn something!</p>
<p>There are likely plenty of videos of the topic, but this one was in my top results, so I watched:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/kGW8Al_cga4?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p><a href="https://www.youtube.com/watch?v=kGW8Al_cga4&amp;list=WL&amp;index=3&amp;t=0s">HTML &amp; CSS Crash Course Tutorial #6 - HTML 5 Semantics</a>.
It&rsquo;s part of a series of videos on the topic of HTML &amp; CSS by the <a href="https://www.youtube.com/channel/UCW5YeuERMmlnqo4oq8vwUpg">Net Ninja</a>.
This particular episode was covering the topic of the semantic tags:</p>
<p>So you have a main tag that wraps the meaty content of your page (ie. not stuff like <code>header</code> / <code>footer</code> / <code>navigation</code>).
Inside, you would put articles, that wrap each piece of content (a blog post, a news article, etc).
Sections tend to be for grouping some other information, like a list of resources, some contact info.
Asides can be related content like similar articles, or something somewhat related to your current article
(perhaps a short bio of a character you&rsquo;re mentioning in your article?)
In the header section, you&rsquo;d put the title of your site, the navigation.
The footer will contain your contact info.</p>
<p>Here&rsquo;s a basic structure of how those tags are organised:</p>
<p><figure>
  <a href="#img-457b6a650e4fa19c9b10a81a222ab147">
    <img src="/img/misc-learning/HTML&#43;5&#43;semantic&#43;tag&#43;structure.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-457b6a650e4fa19c9b10a81a222ab147">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc-learning/HTML&#43;5&#43;semantic&#43;tag&#43;structure.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>After an explanation of those tags, the author does a live demo, building up a web page with all those tags.
So it was a good refresher for me to remember how to use those tags, rather than nesting <code>div</code> after <code>div</code>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Modern web game development</title><link>https://glaforge.dev/posts/2020/04/27/modern-web-game-development/</link><pubDate>Mon, 27 Apr 2020 15:04:09 +0100</pubDate><guid>https://glaforge.dev/posts/2020/04/27/modern-web-game-development/</guid><description>&lt;p>Next in my series of videos while doing sports at home, I watched this video from my colleague &lt;a href="https://twitter.com/tcmg">Tom Greenaway&lt;/a>!
It&amp;rsquo;s about modern web game development, and was recorded last year at Google I/O.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/aVTYxHL45SA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>There are big gaming platforms, like Sony&amp;rsquo;s PlayStation, Microsoft&amp;rsquo;s XBox, Nintendo Switch,
as well as plenty of mobile games on Android and iOS.
But the Web itself, within your browser, is also a great platform for developing and publishing games!
There&amp;rsquo;s all that&amp;rsquo;s needed for good games!&lt;/p></description><content:encoded>
<![CDATA[<p>Next in my series of videos while doing sports at home, I watched this video from my colleague <a href="https://twitter.com/tcmg">Tom Greenaway</a>!
It&rsquo;s about modern web game development, and was recorded last year at Google I/O.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/aVTYxHL45SA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>There are big gaming platforms, like Sony&rsquo;s PlayStation, Microsoft&rsquo;s XBox, Nintendo Switch,
as well as plenty of mobile games on Android and iOS.
But the Web itself, within your browser, is also a great platform for developing and publishing games!
There&rsquo;s all that&rsquo;s needed for good games!</p>
<p>Tom explains that you need a functioning game (runs well on device, looks good, sounds good).
And today, most of the game engines you can use for developing games actually provide an HTML5 target.
You need users, and you need to have a good monetisation strategy.
The web already provides all the right APIs for nice graphics, sound mixing, etc, and its a very open platform for spreading virally.</p>
<p>It was pretty interesting to hear about one of the key advantages of the web: it&rsquo;s URLs!
You can be pretty creative with URLs. A game can create a URL for a given game session,
for a particular state in a game, for inviting others to join.</p>
<p>In addition to game engines with a web target, Tom mentions also that it&rsquo;s possible to port games from C/C++ for example,
to JavaScript in the browser, with a tool like <a href="https://emscripten.org/">Emscripten</a>.
Even things like OpenGL 3D rendering can be translated into WebGL.
But he also advises to look at WebAssembly, as it&rsquo;s really become the new approach to native performance in the browser.
He mentioned <a href="https://www.construct.net/fr">construct</a>, it&rsquo;s basically the Box2D game engine, but optimised for WebAssembly.</p>
<p>For 3D graphics, for the web, the future lies in <a href="https://gpuweb.github.io/gpuweb/">WebGPU</a>,
which is a more modern take on WebGL and OpenGL. For audio,
there&rsquo;s the <a href="https://www.w3.org/TR/webaudio/">Web Audio</a> APIs
and worklets which allows you to even create effects in JavaScript or WebAssembly.
But there are other useful APIs for game development,
like the <a href="https://www.w3.org/TR/gamepad/">Gamepad</a> API, the <a href="https://www.w3.org/TR/gyroscope/">Gyroscope</a> API, etc.</p>
<p>For getting users, ensure that your game is fun of course, but also make it fast, in particular load fast,
to avoid using users even before you actually got them to load the game!
But you also need to think about this user acquisition loop: make the game load and start fast to enter the action right away,
so you&rsquo;re really pulled in in the game, and that&rsquo;s then a good reason for users to share this new cool games with others.
Of course, being featured on game sites &amp; libraries helps, it gives a big boost,
but it&rsquo;s not necessarily what will make you earn the most in the long run.
Tom also shares various examples of games that were successful and worked well.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Decoding a QR code by hand</title><link>https://glaforge.dev/posts/2020/04/26/decoding-a-qr-code-by-hand/</link><pubDate>Sun, 26 Apr 2020 15:07:49 +0100</pubDate><guid>https://glaforge.dev/posts/2020/04/26/decoding-a-qr-code-by-hand/</guid><description>&lt;p>Doing sport at home on a treadmill or an elliptical bike is pretty boring, when you&amp;rsquo;re confined,
so to make things more interesting, I&amp;rsquo;m watching some videos to learn something new while exercising.
This time, I found this old video about how to decode QR codes&amp;hellip; by hand!
Have you ever thought how these were encoded?&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KA8hDldvfv0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>This video comes from the &lt;a href="https://www.youtube.com/channel/UCnqifAqGaat6blFe8wlllOw">Robomatics&lt;/a> YouTube channel.
You recognise easily QR codes thanks to the 3 big squares with the inner white line.
I knew about the purple dotted lines that was fixed in those patterns.
What I didn&amp;rsquo;t know however was that there&amp;rsquo;s a mask that is applied to the data, to avoid QR codes to potentially look all white or black.
It was interesting to see also how the bytes were encoded: how they follow a path through out the matrix.&lt;/p></description><content:encoded>
<![CDATA[<p>Doing sport at home on a treadmill or an elliptical bike is pretty boring, when you&rsquo;re confined,
so to make things more interesting, I&rsquo;m watching some videos to learn something new while exercising.
This time, I found this old video about how to decode QR codes&hellip; by hand!
Have you ever thought how these were encoded?</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KA8hDldvfv0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>This video comes from the <a href="https://www.youtube.com/channel/UCnqifAqGaat6blFe8wlllOw">Robomatics</a> YouTube channel.
You recognise easily QR codes thanks to the 3 big squares with the inner white line.
I knew about the purple dotted lines that was fixed in those patterns.
What I didn&rsquo;t know however was that there&rsquo;s a mask that is applied to the data, to avoid QR codes to potentially look all white or black.
It was interesting to see also how the bytes were encoded: how they follow a path through out the matrix.</p>
<p>However, what this video doesn&rsquo;t cover, for example, is how error correction is working.
You might have some holes or a bad picture of a QR code, but still being able to decode it with some level of loss of data.
So I&rsquo;ll have to learn how that part works some day!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Defence against the Docker arts by Joe Kutner</title><link>https://glaforge.dev/posts/2020/04/24/defence-against-the-docker-arts-by-joe-kutner/</link><pubDate>Fri, 24 Apr 2020 15:34:25 +0100</pubDate><guid>https://glaforge.dev/posts/2020/04/24/defence-against-the-docker-arts-by-joe-kutner/</guid><description>&lt;p>Confined at home, because of the corona-virus pandemic, I&amp;rsquo;m also doing sport at home. I have a small treadmill for light walks (mostly during conf calls!) and also an elliptical bike. I&amp;rsquo;d much rather run outside though, but I have to use what I have, even if I hate that stationary elliptical bike in my basement. It&amp;rsquo;s so boring! So to avoid feeling like wasting my time, I decided to watch videos during my sessions! Not necessarily series on Netflix. No! But interesting technical videos. So today, I&amp;rsquo;d like to share with you a series of posts on those interesting videos I&amp;rsquo;m watching while exercising.&lt;/p></description><content:encoded>
<![CDATA[<p>Confined at home, because of the corona-virus pandemic, I&rsquo;m also doing sport at home. I have a small treadmill for light walks (mostly during conf calls!) and also an elliptical bike. I&rsquo;d much rather run outside though, but I have to use what I have, even if I hate that stationary elliptical bike in my basement. It&rsquo;s so boring! So to avoid feeling like wasting my time, I decided to watch videos during my sessions! Not necessarily series on Netflix. No! But interesting technical videos. So today, I&rsquo;d like to share with you a series of posts on those interesting videos I&rsquo;m watching while exercising.</p>
<p>Today, thanks to the wonderful <a href="https://twitter.com/codefinger">Joe Kutner</a>, from <a href="https://www.heroku.com/">Heroku</a>, I learned about the Defence Against the Docker Arts! It was recorded at Devoxx Belgium 2019.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/ofH9_sE2qy0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Joe starts with clearly differentiating Docker and Dockerfiles. Docker is an ecosystem, while Dockerfiles describe docker container images. An important distinction. The first part of the video, shows best practices on how to writer proper Dockerfiles, and references an article on the Docker blog <a href="https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/">post</a> from last year on that topic: </p>
<ul>
<li>use official base images, rather than reinventing the wheel, as they are usually more up-to-date and secure</li>
<li>remember that images are built in layers, so to speed up your builds, ensure that the base layers are the ones that change less, and keep your source file changes in the last layer as they change the most</li>
<li>join several RUN commands into one by binding them together with ampersands</li>
<li>be explicit about the version of base images you use</li>
<li>try to chose minimal flavors of base images, as they can be pretty big</li>
<li>build from source in a consistent environment, so that developers are on the same page, with the same version of their environment (build tool, runtime versions)</li>
<li>fetch dependencies in a separate step, so dependencies are cached in their own layer</li>
<li>use multi-staged build to remove build dependencies not needed at runtime</li>
</ul>
<p>That&rsquo;s a lot of things to know! Joe then moves on to talk about higher-level approaches, starting with the <a href="https://github.com/moby/buildkit">BuildKit</a> Go library. It&rsquo;s more for platform developers than developers, but it gives you lots of advanced controls on how to build docker images.</p>
<p>Joe introduces <a href="https://github.com/GoogleContainerTools/jib">Jib</a> which is a build plugin (both for Maven and Gradle) that let developers focus on writing and building their apps, but letting that plugin create properly layered docker images for you, using minimal base images. You can even build without a local Docker daemon.</p>
<p>After BuildKit and Jib, Joe talks about the new Cloud Native <a href="https://buildpacks.io/">BuildPacks</a>, a tool that builds OCI images from source, cleverly. There are buildpacks for plenty of platforms and runtimes, not just Java. Those new cloud native buildpacks build upon years of experience from Heroku and CloudFoundry, on the first version of the buildpack concept. Joe says that buildpacks are reusable, fast, modular and safe, and goes on to show the power of this approach that allowed Heroku, for instance, to safely and rapidly upgrade Heartbleed affected images by replacing the underlying OS with a patched / upgraded version, thanks to image rebasing.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Start the fun with Java 14 and Micronaut inside serverless containers on Cloud Run</title><link>https://glaforge.dev/posts/2020/03/24/start-the-fun-with-java-14-and-micronaut-inside-serverless-containers-on-cloud-run/</link><pubDate>Tue, 24 Mar 2020 15:59:29 +0100</pubDate><guid>https://glaforge.dev/posts/2020/03/24/start-the-fun-with-java-14-and-micronaut-inside-serverless-containers-on-cloud-run/</guid><description>&lt;p>Hot on the heels of the &lt;a href="https://mail.openjdk.java.net/pipermail/jdk-dev/2020-March/004089.html">announcement&lt;/a> of the general availability of JDK 14, I couldn&amp;rsquo;t resist taking it for a spin. Without messing up my environment &amp;mdash; I&amp;rsquo;ll confess I&amp;rsquo;m running 11 on my machine, but I&amp;rsquo;m still not even using everything that came past Java 8! &amp;mdash; I decided to test this new edition within the comfy setting of a Docker container.&lt;/p>
&lt;h2 id="minimal-openjdk-14-image-running-jshell">Minimal OpenJDK 14 image running JShell&lt;/h2>
&lt;p>Super easy to get started (assuming you have Docker installed on your machine), create a Dockerfile with the following content:&lt;/p></description><content:encoded>
<![CDATA[<p>Hot on the heels of the <a href="https://mail.openjdk.java.net/pipermail/jdk-dev/2020-March/004089.html">announcement</a> of the general availability of JDK 14, I couldn&rsquo;t resist taking it for a spin. Without messing up my environment &mdash; I&rsquo;ll confess I&rsquo;m running 11 on my machine, but I&rsquo;m still not even using everything that came past Java 8! &mdash; I decided to test this new edition within the comfy setting of a Docker container.</p>
<h2 id="minimal-openjdk-14-image-running-jshell">Minimal OpenJDK 14 image running JShell</h2>
<p>Super easy to get started (assuming you have Docker installed on your machine), create a Dockerfile with the following content:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> openjdk:14</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">CMD</span> [<span style="color:#4070a0">&#34;jshell&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>Only two lines: one to declare an <a href="https://hub.docker.com/_/openjdk">OpenJDK base image</a> with the 14 tag, and one to launch the <a href="https://docs.oracle.com/en/java/javase/14/jshell/introduction-jshell.html">JShell</a> REPL (introduced in Java 9).</p>
<p>Build and tag the image with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ docker build -t 14fun .
</span></span></code></pre></div><p>I called it 14fun, because you could almost pronounce it &ldquo;one for the fun&rdquo;! And then you can run this container image interactively with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ docker run -it 14fun
</span></span></code></pre></div><p>Then you will land directly in JShell, and can try a hello world of sorts:</p>
<pre tabindex="0"><code>Mar 20, 2020 9:17:28 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
| Welcome to JShell -- Version 14
| For an introduction type: /help intro
jshell&gt; System.out.println(&#34;Stay at home!&#34;)
Stay at home!
</code></pre><p>You can enter /exit to quit the REPL.</p>
<p>And you certainly noticed that we&rsquo;re indeed on version 14, congrats!</p>
<h2 id="new-and-preview-features">New and preview features</h2>
<p>If you read the <a href="https://mail.openjdk.java.net/pipermail/jdk-dev/2020-March/004089.html">announcement</a>, you will have remarked that some of the new features are not necessarily generally available, but are still only in preview mode. </p>
<p>Here&rsquo;s what&rsquo;s new:</p>
<ul>
<li><a href="https://openjdk.java.net/jeps/361">improved switch expressions</a> (standard),</li>
<li><a href="https://openjdk.java.net/jeps/305">pattern matching on instanceof</a> (preview), </li>
<li><a href="https://openjdk.java.net/jeps/359">records</a> (preview), and </li>
<li><a href="https://openjdk.java.net/jeps/368">text blocks</a> (second preview).</li>
</ul>
<p>If you want to play with those upcoming features, you have to let the Java tooling know that you want to enable them. You can do that with the &ndash;enable-preview flag. So let&rsquo;s update our Dockerfile accordingly:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> openjdk:14</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">CMD</span> [<span style="color:#4070a0">&#34;jshell&#34;</span>, <span style="color:#4070a0">&#34;--enable-preview&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>Rebuild and rerun the docker commands.</p>
<h3 id="text-blocks">Text blocks</h3>
<p>What about trying the <a href="https://openjdk.java.net/jeps/368">text blocks</a>? With text blocks, don&rsquo;t bother with appending strings with + operations, not forgetting the \n at the end of each line. It&rsquo;s now sweeter to write long strings spanning several lines, for example:</p>
<pre tabindex="0"><code>$ docker run -it 14fun 
Mar 20, 2020 1:12:28 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
| Welcome to JShell -- Version 14
| For an introduction type: /help intro
jshell&gt; var s = &#34;&#34;&#34;
   ...&gt; Hello everyone!
   ...&gt; How are you doing?
   ...&gt; &#34;&#34;&#34;
s ==&gt; &#34;Hello everyone!\nHow are you doing?\n&#34;
</code></pre><h3 id="records">Records</h3>
<p>Java is often criticized for its verbosity &mdash; think for instance how writing good POJOs can be tiresome, with proper equals() / hashCode() / toString() methods, constructors, getters and setters. Fortunately, IDEs help a lot here, but sometimes you really want some simple data holder classes without all the boilerplate. That&rsquo;s where <a href="https://openjdk.java.net/jeps/359">records</a> come into the picture. </p>
<p>Note however that these are not immutable (for example, like in Apache Groovy with its <a href="http://docs.groovy-lang.org/latest/html/gapi/groovy/transform/Immutable.html">@Immutable</a> transformation), if the fields in the record are mutable objects.</p>
<p>Let&rsquo;s imagine a 3D point Record, what would it look like?</p>
<pre tabindex="0"><code>jshell&gt; record Point3D(double x, double y, double z) { }
| created record Point3D

jshell&gt; var p1 = new Point3D(0, 1, 2)
p1 ==&gt; Point3D[x=0.0, y=1.0, z=2.0]

jshell&gt; var p2 = new Point3D(0, 1, 2)
p2 ==&gt; Point3D[x=0.0, y=1.0, z=2.0]

jshell&gt; p1.equals(p2)
$5 ==&gt; true
</code></pre><p>Notice the toString() representation, and that equals() is implemented comparing the values of each field.</p>
<h3 id="improved-switch-expressions">Improved switch expressions</h3>
<p>Switch statements are&hellip; indeed statements, not expressions. It means they didn&rsquo;t so far return any value, or couldn&rsquo;t be passed as parameter values to methods. </p>
<p>Times they are <a href="https://en.wikipedia.org/wiki/The_Times_They_Are_a-Changin%27_(Bob_Dylan_album)">a-changin</a>! Switch borrows the arrow syntax from lambdas to get a&hellip; break from break! And they can be used as values too!</p>
<pre tabindex="0"><code>jshell&gt; var day = &#34;Saturday&#34;
day ==&gt; &#34;Saturday&#34;

jshell&gt; var isWeekend = switch (day) {
   ...&gt; case &#34;Monday&#34;, &#34;Tuesday&#34;, &#34;Wednesday&#34;, 
   ...&gt; &#34;Thursday&#34;, &#34;Friday&#34; -&gt; false;
   ...&gt; case &#34;Saturday&#34;, &#34;Sunday&#34; -&gt; true;
   ...&gt; default -&gt; false;
   ...&gt; }
isWeekend ==&gt; true
</code></pre><h3 id="pattern-matching-on-instanceof">Pattern matching on instanceof</h3>
<p>Working with Apache Groovy for years, whether with its static type checking or dynamic nature, I&rsquo;m quite used to skipping the redundant cast inside if (someObj instanceof SomeObject) {} blocks, thanks to <a href="http://docs.groovy-lang.org/latest/html/documentation/core-semantics.html#_instanceof_inference">smart type inference</a> and flow typing. Java 14 takes a slightly different approach to this problem by with its <a href="https://openjdk.java.net/jeps/305">pattern matching on instanceof</a>, introducing a new local variable of the right type, rather than assuming the variable itself is of the right type. Well, it&rsquo;s better explained with an example:</p>
<pre tabindex="0"><code>jshell&gt; String name = &#34; Guillaume &#34;
name ==&gt; &#34; Guillaume &#34;

jshell&gt; if (name instanceof String nameString) {
   ...&gt; System.out.println(nameString.trim());
   ...&gt; } else {
   ...&gt; System.out.println(&#34;Not a string!&#34;);
   ...&gt; }
Guillaume
</code></pre><h2 id="jdk-14-in-a-serverless-container-with-cloud-run">JDK 14 in a serverless container, with Cloud Run</h2>
<p>Together we discovered the cool new syntax enhancements and constructs, and how we can play with them inside a Docker container image. But what about deploying some Java 14 powered containerized app in the cloud, in a serverless fashion? (ie. transparent scaling from 0 to 1, 1 to n, and back, paying only for what you use). For that purpose, you can easily deploy and scale containers in the cloud thanks to <a href="https://cloud.run/">Cloud Run</a>.</p>
<p>With the launch of Java / JDK 14, also came the first <a href="https://objectcomputing.com/news/2020/03/20/micronaut-20-milestone-1-released">2.0 milestone of the lovely and powerful Micronaut framework</a>! Micronaut is probably the best framework for serverless microservices on the market, thanks to its awesome performance, lightness, in particular regarding super fast startup times. So it&rsquo;s the right occasion to have fun with <a href="https://micronaut.io/">Micronaut</a> again. So let&rsquo;s build a Java 14 application, with Micronaut, running on Cloud Run.</p>
<h3 id="create-a-micronaut-app">Create a Micronaut app</h3>
<p>To get started, have a look at the <a href="https://docs.micronaut.io/latest/guide/index.html#buildCLI">installation guide</a> for Micronaut. In a nutshell, it&rsquo;s using the <a href="https://sdkman.io/">Sdkman</a> tool to manage versions of various SDKs. You can install Sdkman easily:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ curl -s <span style="color:#4070a0">&#34;https://get.sdkman.io&#34;</span> | bash
</span></span></code></pre></div><p>Once installed, you can also install the Micronaut command-line:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sdk install micronaut 2.0.0.M1
</span></span></code></pre></div><p>Next, we&rsquo;ll create an empty app named &ldquo;app&rdquo; with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ mn create-app app
</span></span></code></pre></div><p>The project will be created in the app/ subdirectory, cd into it, to also create a controller, and call it hello:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ mn create-controller hello
</span></span></code></pre></div><p>You&rsquo;ll need to implement the controller, and tweak the app/build.gradle file to enable Java 14&rsquo;s preview features.</p>
<p>Update this section at the bottom of the build file to add the &ndash;enable-preview flag:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>tasks<span style="color:#666">.</span><span style="color:#4070a0">withType</span><span style="color:#666">(</span>JavaCompile<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    options<span style="color:#666">.</span><span style="color:#4070a0">encoding</span> <span style="color:#666">=</span> <span style="color:#4070a0">&#34;UTF-8&#34;</span>
</span></span><span style="display:flex;"><span>    options<span style="color:#666">.</span><span style="color:#4070a0">compilerArgs</span><span style="color:#666">.</span><span style="color:#4070a0">add</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;-parameters&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    options<span style="color:#666">.</span><span style="color:#4070a0">compilerArgs</span><span style="color:#666">.</span><span style="color:#4070a0">add</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;--enable-preview&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Now, open the src/main/java/app/HelloController.java class:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">app</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.MediaType</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.Controller</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.Get</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.Produces</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#555;font-weight:bold">@Controller</span>(<span style="color:#4070a0">&#34;/&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloController</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Produces</span>(MediaType.<span style="color:#4070a0">TEXT_HTML</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#555;font-weight:bold">@Get</span>(<span style="color:#4070a0">&#34;/{day}&#34;</span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span>String<span style="color:#bbb"> </span><span style="color:#06287e">index</span>(String<span style="color:#bbb"> </span>day)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>isWeekend<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">switch</span>(day.<span style="color:#4070a0">toLowerCase</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;saturday&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;sunday&#34;</span><span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">case</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;monday&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;tuesday&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;wednesday&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                 </span><span style="color:#4070a0">&#34;thursday&#34;</span>,<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;friday&#34;</span><span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">default</span><span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">false</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>};<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">return</span><span style="color:#bbb"> </span>String.<span style="color:#4070a0">format</span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#4070a0">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">                        It&#39;s %s, it is %s the weekend!
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">           &#34;&#34;&#34;</span>,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">           </span>day,<span style="color:#bbb"> </span>(isWeekend<span style="color:#bbb"> </span><span style="color:#666">?</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span><span style="color:#bbb"> </span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;not&#34;</span>));<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice how you take advantage of the improved switch expression and the text block!</p>
<h3 id="create-a-docker-image">Create a Docker image</h3>
<p>Micronaut&rsquo;s project template comes with a default Dockerfile, but update it to look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> openjdk:14</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">WORKDIR</span><span style="color:#4070a0"> /app</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">COPY</span> ./ ./<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">RUN</span> ./gradlew shadowJar<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">EXPOSE</span><span style="color:#4070a0"> 8080</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">CMD</span> [<span style="color:#4070a0">&#34;java&#34;</span>, <span style="color:#4070a0">&#34;--enable-preview&#34;</span>, <span style="color:#4070a0">&#34;-jar&#34;</span>, <span style="color:#4070a0">&#34;build/libs/app-0.1-all.jar&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>Then build this container image (with your name of choice) with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker build -t IMAGE_NAME .
</span></span></code></pre></div><p>And check that it runs fine with this docker run command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker run -p 8080:8080 -it IMAGE_NAME
</span></span></code></pre></div><p>Then head over to <a href="http://127.0.0.1/Monday">http://127.0.0.1/Monday</a> or <a href="http://127.0.0.1/Sunday">http://127.0.0.1/Sunday</a> to see if it works fine.</p>
<p>So you now have a working Micronaut 2.0 application, running on JDK 14, using some of the new and preview features of Java 14! Congrats!</p>
<h3 id="scaling-your-container-image-in-the-cloud">Scaling your container image in the cloud</h3>
<p>Time to deploy your Java 14-powered Micronaut web application into the cloud, on Cloud Run. Why <a href="https://cloud.google.com/run/">Cloud Run</a>? Because with Cloud Run, you can easily push a container in production in matters of seconds. It abstracts away all the infrastructure, so you don&rsquo;t have to worry about it. Google Cloud Platform handles it for you, so you can focus on your code instead. You pay proportionally to your usage: it&rsquo;s serveless, so if nobody pings your app, you won&rsquo;t pay anything as no container will be running. But as traffic ramps up, one or more containers will be lined up to serve your requests.</p>
<p>If you haven&rsquo;t already, you can get started on Google Cloud Platform with its <a href="https://cloud.google.com/free">free trial</a> (and free quota). For this tutorial however, you need to create a billing account. Once you have an account ready, create a new GCP project in the Google Cloud <a href="https://console.cloud.google.com/">console</a>. Head over to the Cloud Run section, from the hamburger menu, and click on the &ldquo;Enable API&rdquo; button.</p>
<p>Last thing before heading to the command-line, install the <a href="https://cloud.google.com/sdk/docs/quickstarts">gcloud SDK</a> command-line to work from your terminal. Once gcloud is installed, you can login with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud auth login
</span></span></code></pre></div><p>Set the project name to the one you created in the console:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud config <span style="color:#007020">set</span> project YOUR_PROJECT_ID
</span></span></code></pre></div><p>You&rsquo;ll be using the fully-managed version of Cloud Run:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud config <span style="color:#007020">set</span> run/platform managed
</span></span></code></pre></div><p>Define a default region, for me, that&rsquo;s gonna be europe-west1</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud config <span style="color:#007020">set</span> run/region REGION
</span></span></code></pre></div><p>It&rsquo;s possible to also build container images in <a href="https://cloud.google.com/cloud-build">Cloud Build</a> (see some <a href="https://cloud.google.com/run/docs/quickstarts/build-and-deploy">instructions</a> that show this), but here you are using Docker locally to build your images. So let&rsquo;s configure the Docker integration and Container Registry access with the following commands:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud auth configure-docker
</span></span><span style="display:flex;"><span>gcloud components install docker-credential-gcr
</span></span></code></pre></div><p>Tag your image with the following naming convention:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker build . --tag gcr.io/YOUR_PROJECT_ID/IMAGE_NAME
</span></span></code></pre></div><p>Let&rsquo;s push our image to Container Registry (and change the image and project names accordingly):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker push gcr.io/YOUR_PROJECT_ID/IMAGE_NAME
</span></span><span style="display:flex;"><span>gcloud run deploy weekend-service<span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span> --image gcr.io/YOUR_PROJECT_ID/IMAGE_NAME
</span></span><span style="display:flex;"><span> --allow-unauthenticated
</span></span></code></pre></div><p>You should see output similar to this, showing the URL where you can access your app:</p>
<pre tabindex="0"><code>Deploying container to Cloud Run service [weekend-service] in project [YOUR_PROJECT_ID] region [europe-west1]
✓ Deploying new service... Done.
 ✓ Creating Revision...
 ✓ Routing traffic...
 ✓ Setting IAM Policy...
Done.
Service [weekend-service] revision [weekend-service-00001-xig] has been deployed and is serving 100 percent of traffic at https://weekend-service-brxby8yoda-ew.a.run.app
</code></pre><p>Navigate to that URL, append the name of the day, and check whether it&rsquo;s weekend time!</p>
<h2 id="and-voilà">And voilà! </h2>
<p>Less than a minute later, your Java 14 + Micronaut container app has been deployed to Cloud Run. Automatically, you got a secured HTTPS endpoint for your app (you can also provide your own domain name), without bothering with the infrastructure and scaling aspects.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #7 — Create mini APIs with Cloud Functions and Express routing</title><link>https://glaforge.dev/posts/2019/12/17/serverless-tip-7-create-mini-apis-with-cloud-functions-and-express-routing/</link><pubDate>Tue, 17 Dec 2019 16:04:34 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/17/serverless-tip-7-create-mini-apis-with-cloud-functions-and-express-routing/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account and project&lt;/li>
&lt;li>Cloud Functions should be enabled for that project&lt;/li>
&lt;/ul>
&lt;p>Compared to the previous tip when using Exress&amp;rsquo; request path attribute, we can take advantage of Express routing.&lt;/p>
&lt;p>So to support the following paths:&lt;/p>
&lt;pre tabindex="0">&lt;code>https://us-central1-myproject.cloudfunctions.net/api/customers
https://us-central1-myproject.cloudfunctions.net/api/customers/32
https://us-central1-myproject.cloudfunctions.net/api/customers/32/address
&lt;/code>&lt;/pre>&lt;p>We can have our functions require Express by adding Express in &lt;code>package.json&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>  &lt;span style="color:#062873;font-weight:bold">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#4070a0">&amp;#34;mini-api-router&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>  &lt;span style="color:#062873;font-weight:bold">&amp;#34;version&amp;#34;&lt;/span>: &lt;span style="color:#4070a0">&amp;#34;0.0.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>  &lt;span style="color:#062873;font-weight:bold">&amp;#34;dependencies&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>    &lt;span style="color:#062873;font-weight:bold">&amp;#34;express&amp;#34;&lt;/span>: &lt;span style="color:#4070a0">&amp;#34;^4.17.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>  }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we can require that dependency in our new functions script:&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account and project</li>
<li>Cloud Functions should be enabled for that project</li>
</ul>
<p>Compared to the previous tip when using Exress&rsquo; request path attribute, we can take advantage of Express routing.</p>
<p>So to support the following paths:</p>
<pre tabindex="0"><code>https://us-central1-myproject.cloudfunctions.net/api/customers
https://us-central1-myproject.cloudfunctions.net/api/customers/32
https://us-central1-myproject.cloudfunctions.net/api/customers/32/address
</code></pre><p>We can have our functions require Express by adding Express in <code>package.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;mini-api-router&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;version&#34;</span>: <span style="color:#4070a0">&#34;0.0.1&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;dependencies&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;express&#34;</span>: <span style="color:#4070a0">&#34;^4.17.1&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then we can require that dependency in our new functions script:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// we use express explicitly
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> express <span style="color:#666">=</span> require(<span style="color:#4070a0">&#39;express&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> app <span style="color:#666">=</span> express();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// some customer data retrieved from Firestore or elsewhere
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> customers <span style="color:#666">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;32&#34;</span><span style="color:#666">:</span> { name<span style="color:#666">:</span> <span style="color:#4070a0">&#39;Alice&#39;</span>, address<span style="color:#666">:</span> <span style="color:#4070a0">&#39;21 Jump Street&#39;</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;33&#34;</span><span style="color:#666">:</span> { name<span style="color:#666">:</span> <span style="color:#4070a0">&#39;Bob&#39;</span>, address<span style="color:#666">:</span> <span style="color:#4070a0">&#39;1 Main Street&#39;</span> }
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// this time we can define the path easily
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>app.get(<span style="color:#4070a0">&#39;/&#39;</span>, (req, res) =&gt; 
</span></span><span style="display:flex;"><span>        res.send(<span style="color:#4070a0">&#39;Hello World!&#39;</span>));
</span></span><span style="display:flex;"><span>app.get(<span style="color:#4070a0">&#39;/customers&#39;</span>, (req, res) =&gt; 
</span></span><span style="display:flex;"><span>        res.status(<span style="color:#40a070">200</span>).json(customers).end());
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// we can also specify path variables like :id 
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// that we can retrieve via the request params object
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>app.get(<span style="color:#4070a0">&#39;/customers/:id&#39;</span>, (req, res) =&gt; 
</span></span><span style="display:flex;"><span>        res.status(<span style="color:#40a070">200</span>).json(customers[req.params.id]).end());
</span></span><span style="display:flex;"><span>app.get(<span style="color:#4070a0">&#39;/customers/:id/address&#39;</span>, (req, res) =&gt; 
</span></span><span style="display:flex;"><span>        res.status(<span style="color:#40a070">200</span>).json({address<span style="color:#666">:</span> customers[req.params.id].address}).end());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// we need to export the app object for Cloud Functions to expose it
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>exports.api <span style="color:#666">=</span> app;
</span></span></code></pre></div><p>More information:</p>
<ul>
<li><a href="https://expressjs.com/">Express framework</a></li>
<li>Express <a href="http://expressjs.com/en/guide/routing.html#routing">routing</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #6 — Create a mini web API with Cloud Functions</title><link>https://glaforge.dev/posts/2019/12/17/serverless-tip-6-create-a-mini-web-api-with-cloud-functions/</link><pubDate>Tue, 17 Dec 2019 16:01:33 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/17/serverless-tip-6-create-a-mini-web-api-with-cloud-functions/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account and project&lt;/li>
&lt;li>Cloud Functions should be enabled for that project&lt;/li>
&lt;/ul>
&lt;p>We often use individual HTTP &lt;a href="https://cloud.google.com/functions/">Cloud Functions&lt;/a> as a single endpoint, and we pass data to the functions with either query parameters, or via a POST body payload. Although it&amp;rsquo;s a good practice to keep the scope of a function small, however, you can easily write mini Web APIs for a given function, with different paths for different needs, like with usual Web frameworks.&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account and project</li>
<li>Cloud Functions should be enabled for that project</li>
</ul>
<p>We often use individual HTTP <a href="https://cloud.google.com/functions/">Cloud Functions</a> as a single endpoint, and we pass data to the functions with either query parameters, or via a POST body payload. Although it&rsquo;s a good practice to keep the scope of a function small, however, you can easily write mini Web APIs for a given function, with different paths for different needs, like with usual Web frameworks.</p>
<p>So instead of having just a single endpoint with:</p>
<pre tabindex="0"><code>https://us-central1-myproject.cloudfunctions.net/myfunction
</code></pre><p>You can have sub-paths below the name of your function:</p>
<pre tabindex="0"><code>https://us-central1-myproject.cloudfunctions.net/myapi/customers
https://us-central1-myproject.cloudfunctions.net/myapi/customers/32
https://us-central1-myproject.cloudfunctions.net/myapi/customers/32/address
</code></pre><p>Let&rsquo;s have a look at the Node functions runtime, and how you can implement this approach. The key trick here is to use the request path: req.path, which will give you the <code>/customers/32</code> part of the fully qualified URL.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// some customer data retrieved from Firestore or elsewhere
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="color:#007020;font-weight:bold">const</span> customers <span style="color:#666">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;32&#34;</span><span style="color:#666">:</span> { name<span style="color:#666">:</span> <span style="color:#4070a0">&#39;Alice&#39;</span>, address<span style="color:#666">:</span> <span style="color:#4070a0">&#39;21 Jump Street&#39;</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;33&#34;</span><span style="color:#666">:</span> { name<span style="color:#666">:</span> <span style="color:#4070a0">&#39;Bob&#39;</span>, address<span style="color:#666">:</span> <span style="color:#4070a0">&#39;1 Main Street&#39;</span> }
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>exports.myapi <span style="color:#666">=</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">if</span> (req.path.startsWith(<span style="color:#4070a0">&#39;/customers&#39;</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">const</span> pathElements <span style="color:#666">=</span> req.path.split(<span style="color:#4070a0">&#39;/&#39;</span>) <span style="color:#60a0b0;font-style:italic">// split along the slashes
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>      .filter(e =&gt; e)                        <span style="color:#60a0b0;font-style:italic">// remove the empty strings in the array
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>      .splice(<span style="color:#40a070">1</span>);                            <span style="color:#60a0b0;font-style:italic">// remove the first &#34;customers&#34; element
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#60a0b0;font-style:italic">// path: /customers
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">if</span> (pathElements.length <span style="color:#666">==</span> <span style="color:#40a070">0</span>) { 
</span></span><span style="display:flex;"><span>      <span style="color:#60a0b0;font-style:italic">// return all customers
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>      res.status(<span style="color:#40a070">200</span>).json(customers).end();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// path: /customers/32
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">else</span> <span style="color:#007020;font-weight:bold">if</span> (pathElements.length <span style="color:#666">==</span> <span style="color:#40a070">1</span>) {
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#40a070">200</span>).json(customers[pathElements[<span style="color:#40a070">0</span>]]).end();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// path: /customers/33/address
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#007020;font-weight:bold">else</span> <span style="color:#007020;font-weight:bold">if</span> (pathElements.length <span style="color:#666">==</span> <span style="color:#40a070">2</span> <span style="color:#666">&amp;&amp;</span> pathElements[<span style="color:#40a070">1</span>] <span style="color:#666">==</span> <span style="color:#4070a0">&#34;address&#34;</span>) {
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#40a070">200</span>).json({address<span style="color:#666">:</span> customers[pathElements[<span style="color:#40a070">0</span>]].address}).end();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  res.status(<span style="color:#40a070">404</span>).send(<span style="color:#4070a0">&#39;Unknown path&#39;</span>).end();
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>In the Node.JS runtime, Cloud Functions uses the Express framework under the hood. We have access to the request object, which has lots of useful attributes, including the path.</p>
<p>In this simplistic example, we are using this path attribute directly, but it&rsquo;s also possible to use more advanced routing capabilities, as we shall see in a forthcoming tip.</p>
<p>More information</p>
<ul>
<li><a href="https://expressjs.com/">Express framework</a></li>
<li>Node&rsquo;s request <a href="https://expressjs.com/en/api.html#req.path">path</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #5 — How to invoke a secured Cloud Run service locally</title><link>https://glaforge.dev/posts/2019/12/11/serverless-tip-5-how-to-invoke-a-secured-cloud-run-service-locally/</link><pubDate>Wed, 11 Dec 2019 16:36:39 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/11/serverless-tip-5-how-to-invoke-a-secured-cloud-run-service-locally/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account with a project&lt;/li>
&lt;li>you have enabled the Cloud Run service and already deployed a container image&lt;/li>
&lt;li>your local environment&amp;rsquo;s gcloud is already configured to point at your GCP project&lt;/li>
&lt;/ul>
&lt;p>By default, when you deploy a &lt;a href="https://cloud.run/">Cloud Run&lt;/a> service, it is secured by default, unless you use the &amp;ndash;allow-unauthenticated flag when using the gcloud command-line (or the appropriate checkbox on the Google Cloud Console).&lt;/p>
&lt;p>But once deployed, if you want to call it locally from your development machine, for testing purpose, you&amp;rsquo;ll have to be authenticated.&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account with a project</li>
<li>you have enabled the Cloud Run service and already deployed a container image</li>
<li>your local environment&rsquo;s gcloud is already configured to point at your GCP project</li>
</ul>
<p>By default, when you deploy a <a href="https://cloud.run/">Cloud Run</a> service, it is secured by default, unless you use the &ndash;allow-unauthenticated flag when using the gcloud command-line (or the appropriate checkbox on the Google Cloud Console).</p>
<p>But once deployed, if you want to call it locally from your development machine, for testing purpose, you&rsquo;ll have to be authenticated.</p>
<p>If you look at the Cloud Console, alongside the URL of the service, you can hover the little icon next to the URL, and you&rsquo;ll see the a pop-up showing how you can invoke that service with a curl command:</p>
<p><figure>
  <a href="#img-fa2e1e8051d795b94a85ba4597cf469d">
    <img src="/img/serverless-tips/st5-cr-call-authenticated-service.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-fa2e1e8051d795b94a85ba4597cf469d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/serverless-tips/st5-cr-call-authenticated-service.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -H <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    <span style="color:#4070a0">&#34;Authorization: Bearer </span><span style="color:#007020;font-weight:bold">$(</span>gcloud auth print-identity-token<span style="color:#007020;font-weight:bold">)</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    https://authenticated-x2rq3lgmra-uc.a.run.app
</span></span></code></pre></div><p>Note how a bearer token generated by the gcloud command is passed as header to the curl request.</p>
<p>More information</p>
<ul>
<li><a href="https://cloud.run/">https://cloud.run</a>, the serverless container platform</li>
<li><a href="https://cloud.google.com/run/docs/authenticating/overview">Authentication overview documentation</a></li>
<li><a href="https://cloud.google.com/run/docs/authenticating/public">Unauthenticated invocations</a> of your services</li>
<li><a href="https://cloud.google.com/sdk/gcloud/reference/auth/print-identity-token">gcloud auth print-identity-token</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>8 production-ready features you'll find in Cloud Run fully managed</title><link>https://glaforge.dev/posts/2019/12/11/8-production-ready-features-you-ll-find-in-cloud-run-fully-managed/</link><pubDate>Wed, 11 Dec 2019 13:42:59 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/11/8-production-ready-features-you-ll-find-in-cloud-run-fully-managed/</guid><description>&lt;p>Since we &lt;a href="https://cloud.google.com/blog/products/serverless/announcing-cloud-run-the-newest-member-of-our-serverless-compute-stack">launched Cloud Run&lt;/a> at Google Cloud Next in April, developers have discovered that &amp;ldquo;serverless&amp;rdquo; and &amp;ldquo;containers&amp;rdquo; run well together. With &lt;a href="https://cloud.google.com/run/">Cloud Run&lt;/a>, not only do you benefit from fully managed infrastructure, up and down auto-scaling, and pay-as-you-go pricing, but you&amp;rsquo;re also able to package your workload however you like, inside a stateless container listening for incoming requests, with any language, runtime, or library of your choice. And you get all this without compromising portability, thanks to its &lt;a href="https://knative.dev/">Knative&lt;/a> open-source underpinnings. &lt;/p></description><content:encoded>
<![CDATA[<p>Since we <a href="https://cloud.google.com/blog/products/serverless/announcing-cloud-run-the-newest-member-of-our-serverless-compute-stack">launched Cloud Run</a> at Google Cloud Next in April, developers have discovered that &ldquo;serverless&rdquo; and &ldquo;containers&rdquo; run well together. With <a href="https://cloud.google.com/run/">Cloud Run</a>, not only do you benefit from fully managed infrastructure, up and down auto-scaling, and pay-as-you-go pricing, but you&rsquo;re also able to package your workload however you like, inside a stateless container listening for incoming requests, with any language, runtime, or library of your choice. And you get all this without compromising portability, thanks to its <a href="https://knative.dev/">Knative</a> open-source underpinnings. </p>
<p>Many Google Cloud customers already use Cloud Run in production, for example, to deploy public websites or APIs, or as a way to perform fast and lightweight data transformations or background operations. </p>
<p><em>&ldquo;Cloud Run promises to dramatically reduce the operational complexity of deploying containerized software. The ability to put an automatically scaling service in production with one command is very attractive.&quot;</em> - Jamie Talbot, Principal Engineer at Mailchimp.</p>
<p><a href="https://cloud.google.com/blog/products/serverless/knative-based-cloud-run-services-are-ga">Cloud Run recently became generally available</a>, as both a fully managed platform or on <a href="https://cloud.google.com/anthos/">Anthos</a>, and offers a bunch of new features. What are those new capabilities? Today, let&rsquo;s take a look at what&rsquo;s new in the fully managed Cloud Run platform.</p>
<h2 id="1-service-level-agreement">1. Service level agreement</h2>
<p>With general availability, Cloud Run now comes with a <a href="https://cloud.google.com/run/sla">Service Level Agreement</a> (SLA). In addition, it now offers <a href="https://cloud.google.com/terms/service-terms">data location commitments</a> that allow you to store customer data in a specific region/multi-region. </p>
<h2 id="2-available-in-9-gcp-regions">2. Available in 9 GCP regions</h2>
<p>In addition to South Carolina, Iowa, Tokyo, and Belgium, in the coming weeks, you&rsquo;ll also be able to deploy containers to Cloud Run in North Virginia, Oregon, Netherlands, Finland, and Taiwan, for a total of nine <a href="https://cloud.google.com/about/locations">cloud regions</a>.</p>
<p><figure>
  <a href="#img-f7b1614529316f586eac6a9a3e1c4325">
    <img src="/img/8cr/Cloud_run_regions.max-2000x2000.png"
      alt="/img/8cr/Cloud_run_regions.max-2000x2000.png"
       />
  </a>
  <figcaption>/img/8cr/Cloud_run_regions.max-2000x2000.png</figcaption>
</figure>
<div class="lightbox" id="img-f7b1614529316f586eac6a9a3e1c4325">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/8cr/Cloud_run_regions.max-2000x2000.png"
    alt="/img/8cr/Cloud_run_regions.max-2000x2000.png"
     />
  <div class="lightbox-caption">/img/8cr/Cloud_run_regions.max-2000x2000.png</div>
</div>
</p>
<h2 id="3-max-instances">3. Max instances</h2>
<p>Auto-scaling can be magic, but there are times when you want to limit the maximum number of instances of your Cloud Run services, for example, to limit costs. Or imagine a backend service like a database is limited to a certain number of connections&mdash;you might want to limit the number of instances that can connect to that service. With the <a href="https://cloud.google.com/run/docs/configuring/max-instances">max instance</a> feature, you can now set such a limit.</p>
<p>Use the Cloud Console or Cloud SDK to set this limit:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud run services update SERVICE-NAME --max-instances <span style="color:#40a070">42</span>
</span></span></code></pre></div><h2 id="4-more-secure-https-only">4. More secure: HTTPS only</h2>
<p>All fully managed Cloud Run services receive a stable and secure URL. Cloud Run now only accepts secure HTTPS connection and redirects any HTTP connection to the HTTPS endpoint. </p>
<p>But having an HTTPS endpoint does not mean that your service is publicly accessible&mdash;you are in control and can opt into <a href="https://cloud.google.com/run/docs/authenticating/public">allowing public access</a> to your service. Alternatively, you can <a href="https://cloud.google.com/run/docs/authenticating/overview">require authentication</a> by leveraging the &ldquo;Cloud Run Invoker&rdquo; IAM role.</p>
<h2 id="5-unary-grpc-protocol-support">5. Unary gRPC protocol support</h2>
<p>Cloud Run now lets you deploy and run <a href="https://grpc.io/docs/guides/concepts/">unary gRPC</a> services (i.e., non-streaming gRPC), allowing your microservices to leverage this RPC framework. </p>
<p>To learn more, read Peter Malinas&rsquo; tutorial on <a href="https://medium.com/@petomalina/%EF%B8%8Fserverless-grpc-with-cloud-run-bab3622a47da">Serverless gRPC with Cloud Run</a> using Go, as well as Ahmet Alp Balkan&rsquo;s article on <a href="https://ahmet.im/blog/grpc-auth-cloud-run/">gRPC authentication on Cloud Run</a>.</p>
<h2 id="6-new-metrics-to-track-your-instances">6. New metrics to track your instances</h2>
<p>Out of the box, Cloud Run integrates with <a href="https://cloud.google.com/monitoring/">Stackdriver Monitoring</a>. From within the Google Cloud Console, the Cloud Run page now includes a new &ldquo;Metrics&rdquo; tab that shows charts of key performance indicators for your Cloud Run service: requests per second, request latency, used instance time, CPU and memory.</p>
<p>A new built-in Stackdriver metric called <a href="https://cloud.google.com/monitoring/api/metrics_gcp#gcp-run"><code>container/billable_instance_time</code></a> gives you insights into the number of container instances for a service, with the billable time aggregated from all container instances.</p>
<p><figure>
  <a href="#img-5edda3b8f7af1f055bc291c38b19ce70">
    <img src="/img/8cr/billable_container_instance_time.max-1200x1200.jpg"
      alt="/img/8cr/billable_container_instance_time.max-1200x1200.jpg"
       />
  </a>
  <figcaption>/img/8cr/billable_container_instance_time.max-1200x1200.jpg</figcaption>
</figure>
<div class="lightbox" id="img-5edda3b8f7af1f055bc291c38b19ce70">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/8cr/billable_container_instance_time.max-1200x1200.jpg"
    alt="/img/8cr/billable_container_instance_time.max-1200x1200.jpg"
     />
  <div class="lightbox-caption">/img/8cr/billable_container_instance_time.max-1200x1200.jpg</div>
</div>
</p>
<h2 id="7-labels">7. Labels</h2>
<p>Like the bibs that identify the runners in a race, GCP <a href="https://cloud.google.com/run/docs/configuring/labels">labels</a> can help you easily identify a set of services, break down costs, or distinguish different environments.</p>
<p>You can set labels from the Cloud Run service list page in Cloud Console, or update labels with this command and flag:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud run services update SERVICE-NAME --update-labels <span style="color:#bb60d5">KEY</span><span style="color:#666">=</span>VALUE
</span></span></code></pre></div><h2 id="8-terraform-support">8. Terraform support</h2>
<p>Finally, if you practice <a href="https://cloud.google.com/solutions/infrastructure-as-code/">Infrastructure as Code</a>, you&rsquo;ll be glad to know that <a href="https://www.terraform.io/docs/providers/google/r/cloud_run_service.html">Terraform now  supports Cloud Run</a>, allowing you to provision Cloud Run services from a Terraform configuration. </p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-hcl" data-lang="hcl"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">resource</span> <span style="color:#4070a0">&#34;google_cloud_run_service&#34; &#34;default&#34;</span> {
</span></span><span style="display:flex;"><span>    name     <span style="color:#666">=</span> <span style="color:#4070a0">&#34;hello&#34;</span>
</span></span><span style="display:flex;"><span>    location <span style="color:#666">=</span> <span style="color:#4070a0">&#34;us-central1&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">template</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#007020;font-weight:bold">spec</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#007020;font-weight:bold">containers</span> {
</span></span><span style="display:flex;"><span>                image <span style="color:#666">=</span> <span style="color:#4070a0">&#34;gcr.io/cloudrun/hello&#34;</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="ready-set-go">Ready, set, go!</h2>
<p>The baton is now in your hands. To start deploying your container images to Cloud Run, head over to our quickstart guides on <a href="https://cloud.google.com/run/docs/quickstarts/build-and-deploy">building and deploying your images</a>. With the <a href="https://cloud.google.com/free/">always free tier</a> and the $300 credit for new GCP accounts, you&rsquo;re ready to take Cloud Run for a spin. To learn more, there&rsquo;s the <a href="https://cloud.google.com/run/docs/">documentation</a> of course, as well as the <a href="https://github.com/GoogleCloudPlatform/cloud-run-samples">numerous samples</a> with different language runtimes (don&rsquo;t miss the &ldquo;Run on Google Cloud&rdquo; <a href="https://github.com/GoogleCloudPlatform/cloud-run-button">button</a> to automatically deploy your code). In addition, be sure to check out the community-contributed resources on the <a href="https://github.com/steren/awesome-cloudrun">Awesome Cloud Run</a> github project. We&rsquo;re looking forward to seeing what you build and deploy!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #4 — Discover the full URL of your deployed Cloud Run services with gcloud format flag</title><link>https://glaforge.dev/posts/2019/12/05/serverless-tip-4-discover-the-full-url-of-your-deployed-cloud-run-services-with-gcloud-format-flag/</link><pubDate>Thu, 05 Dec 2019 16:39:56 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/05/serverless-tip-4-discover-the-full-url-of-your-deployed-cloud-run-services-with-gcloud-format-flag/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account&lt;/li>
&lt;li>you have enabled the Cloud Run service and deployed already a container image&lt;/li>
&lt;/ul>
&lt;p>One of the nice things with Cloud Run is that when you deploy your services, you get a URL like &lt;a href="https://myservice-8oafjf26aq-ew.a.run.app/">https://myservice-8oafjf26aq-ew.a.run.app/&lt;/a>, with a certificate, on the run.app domain name, etc.&lt;/p>
&lt;p>You see the name of the service: myservice, the region shortcut where it was deployed: ew (Europe West), and then .a.run.app. However, you can&amp;rsquo;t guess ahead of time what the final URL will be, as there is a randomly generated part in the URL (here: &lt;code>8oafjf26aq&lt;/code>). Let&amp;rsquo;s see how we can discover this whole URL.&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account</li>
<li>you have enabled the Cloud Run service and deployed already a container image</li>
</ul>
<p>One of the nice things with Cloud Run is that when you deploy your services, you get a URL like <a href="https://myservice-8oafjf26aq-ew.a.run.app/">https://myservice-8oafjf26aq-ew.a.run.app/</a>, with a certificate, on the run.app domain name, etc.</p>
<p>You see the name of the service: myservice, the region shortcut where it was deployed: ew (Europe West), and then .a.run.app. However, you can&rsquo;t guess ahead of time what the final URL will be, as there is a randomly generated part in the URL (here: <code>8oafjf26aq</code>). Let&rsquo;s see how we can discover this whole URL.</p>
<p>From my terminal, I can request the list of deployed services (here, on the fully managed Cloud Run):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud run services list <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>       --platform managed
</span></span></code></pre></div><p>It&rsquo;s going to show me something like the following output:</p>
<pre tabindex="0"><code>   SERVICE      REGION        URL                                        LAST DEPLOYED BY     LAST DEPLOYED AT
✔  myservice    europe-west1  https://myservice-8oafjf26aq-ew.a.run.app  myself@foobar.com    2019-11-20T15:26:39.442Z
</code></pre><p>When describing the specific service (I had to specify the region as well, but you can set it by default if needed to avoid repeating yourself):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud run services describe myservice <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>       --platform managed <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>       --region europe-west1
</span></span></code></pre></div><p>You&rsquo;ll see:</p>
<pre tabindex="0"><code>✔ Service hello in region europe-west1
https://myservice-8oafjf26aq-ew.a.run.app
Traffic:
  100%               LATEST (currently myservice-00002-dox)
Last updated on 2019-11-20T15:26:39.442Z by myself@foobar.com:
  Revision myservice-00002-dox
  Image:             gcr.io/my-sample-project/my-container-image:latest
</code></pre><p>So instead of parsing that ourselves somehow, there&rsquo;s a built-in way to get just the info we want, with the useful <code>--format</code> flag:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud run services describe myservice <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --format<span style="color:#666">=</span><span style="color:#4070a0">&#39;value(status.url)&#39;</span> <span style="color:#4070a0;font-weight:bold">\
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-weight:bold"></span>    --platform managed --region europe-west1 
</span></span></code></pre></div><p>This time, in output, you&rsquo;ll get just the URL, which you can then export or reuse with other commands.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>https://myservice-8oafjf26aq-ew.a.run.app
</span></span></code></pre></div><p>The <code>glcoud</code> command provides three useful mechanisms to filter, format, or project the output and values returned. Here, we took advantage of format.</p>
<p>More information:</p>
<ul>
<li><a href="https://cloud.run/">https://cloud.run</a>, the serverless container platform</li>
<li><a href="https://cloud.google.com/blog/products/gcp/filtering-and-formatting-fun-with">Filtering and formatting fun with gcloud, GCP&rsquo;s command line interface</a></li>
<li><a href="https://cloud.google.com/sdk/gcloud/reference/topic/filters">gcloud filters</a></li>
<li><a href="https://cloud.google.com/sdk/gcloud/reference/topic/formats">gcloud formats</a></li>
<li><a href="https://cloud.google.com/sdk/gcloud/reference/topic/projections">gcloud projections</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #3 — Use the Cloud Run button on your Git repository to deploy your project in a click</title><link>https://glaforge.dev/posts/2019/12/04/serverless-tip-3-use-the-cloud-run-button-on-your-git-repository-to-deploy-your-project-in-a-click/</link><pubDate>Wed, 04 Dec 2019 16:44:23 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/04/serverless-tip-3-use-the-cloud-run-button-on-your-git-repository-to-deploy-your-project-in-a-click/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account&lt;/li>
&lt;li>a Git or Github repository containing your project&lt;/li>
&lt;li>your project can have a Dockerfile (but not mandatory)&lt;/li>
&lt;/ul>
&lt;p>With &lt;a href="https://cloud.run/">Cloud Run&lt;/a>, you can easily deploy a container image and let it scale up and down as needed, in a serverless fashion:&lt;/p>
&lt;ul>
&lt;li>No need to focus on infrastructure (provisioning servers, clusters, upgrading OS, etc.)&lt;/li>
&lt;li>Your application can scale transparently from 0 to 1, and from 1 to n (no need for a pager when your app is featured on Hackernews)&lt;/li>
&lt;li>You pay as you go, proportionally to the usage&lt;/li>
&lt;/ul>
&lt;p>If your project is hosted on Github, for example, how can you help users get started with your project? You usually explain all the steps needed to build a container image, or where to fetch a pre-made image from a hub, and then steps to actually deploy that image on the platform. But thanks to the Cloud Run button, you can add a button image on your README.md page for instance, and then users can click on it and get started with building and deploying to a GCP project automagically.&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account</li>
<li>a Git or Github repository containing your project</li>
<li>your project can have a Dockerfile (but not mandatory)</li>
</ul>
<p>With <a href="https://cloud.run/">Cloud Run</a>, you can easily deploy a container image and let it scale up and down as needed, in a serverless fashion:</p>
<ul>
<li>No need to focus on infrastructure (provisioning servers, clusters, upgrading OS, etc.)</li>
<li>Your application can scale transparently from 0 to 1, and from 1 to n (no need for a pager when your app is featured on Hackernews)</li>
<li>You pay as you go, proportionally to the usage</li>
</ul>
<p>If your project is hosted on Github, for example, how can you help users get started with your project? You usually explain all the steps needed to build a container image, or where to fetch a pre-made image from a hub, and then steps to actually deploy that image on the platform. But thanks to the Cloud Run button, you can add a button image on your README.md page for instance, and then users can click on it and get started with building and deploying to a GCP project automagically.</p>
<p><figure>
  <a href="#img-00b4c3c75dbf681ef99ef84e3dafb221">
    <img src="https://deploy.cloud.run/button.svg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-00b4c3c75dbf681ef99ef84e3dafb221">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://deploy.cloud.run/button.svg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>If the Git repository contains a Dockerfile, it will be built using the docker build command. Otherwise, the CNCF Buildpacks (with the pack build command) will be used to build the repository.</p>
<p>The Cloud Run button github project gives extra information on the parameterization of the deploy URL of the button, for example if you want to specify a particular branch or directory.</p>
<p>More information</p>
<ul>
<li><a href="https://cloud.run/">https://cloud.run</a>, the serverless container platform</li>
<li>Cloud Run button <a href="https://github.com/GoogleCloudPlatform/cloud-run-button">github project</a></li>
<li>Cloud Run button <a href="https://cloud.google.com/blog/products/serverless/introducing-cloud-run-button-click-to-deploy-your-git-repos-to-google-cloud">announcement</a></li>
<li><a href="https://buildpacks.io/">CNFC Buildpacks</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #2 — Deploy an executable JVM application with gcloud without app.yaml or build tool plugin</title><link>https://glaforge.dev/posts/2019/12/03/serverless-tip-2-deploy-an-executable-jvm-application-with-gcloud-without-app-yaml-or-build-tool-plugin/</link><pubDate>Tue, 03 Dec 2019 16:46:44 +0100</pubDate><guid>https://glaforge.dev/posts/2019/12/03/serverless-tip-2-deploy-an-executable-jvm-application-with-gcloud-without-app-yaml-or-build-tool-plugin/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account and project&lt;/li>
&lt;li>a Java or alternative language web application&lt;/li>
&lt;li>a build that creates a standalone executable JAR file&lt;/li>
&lt;/ul>
&lt;p>Usually App Engine applications in Java are deployed with the gcloud command-line interface, or via a Maven or Gradle build plugin.
Either way, an &lt;code>app.yaml&lt;/code> file to describe your application is required to let the cloud SDK know that the project at hand is an App Engine project.&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account and project</li>
<li>a Java or alternative language web application</li>
<li>a build that creates a standalone executable JAR file</li>
</ul>
<p>Usually App Engine applications in Java are deployed with the gcloud command-line interface, or via a Maven or Gradle build plugin.
Either way, an <code>app.yaml</code> file to describe your application is required to let the cloud SDK know that the project at hand is an App Engine project.</p>
<p>With the Java 11 runtime, however, it&rsquo;s possible to deploy a standalone executable JAR without <code>app.yaml</code>.
The <code>gcloud app deploy</code> command now takes also a path to a standalone JAR file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud app deploy path/to/app.jar
</span></span></code></pre></div><p>App Engine will automatically assume that you are deploying to the Java 11 runtime, using an F1 instance (256MB of RAM and 600MHz of CPU).
So this deployment would be equivalent to having a simple app.yaml file as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">runtime</span>:<span style="color:#bbb"> </span>java11<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">instance_class</span>:<span style="color:#bbb"> </span>F1<span style="color:#bbb">
</span></span></span></code></pre></div><p>More information:</p>
<ul>
<li><a href="https://cloud.google.com/sdk/gcloud/reference/app/deploy">gcloud app deploy</a> command details</li>
<li><a href="https://cloud.google.com/appengine/docs/standard/java11/config/appref">app.yaml</a></li>
<li><a href="https://cloud.google.com/blog/products/application-development/app-engine-java-11-is-ga-deploy-a-jar-scale-it-all-fully-managed">Announcement and example with Maven and Spring</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Serverless tip #1 — Deploy a standalone JVM web app with Gradle and the App Engine plugin</title><link>https://glaforge.dev/posts/2019/11/29/serverless-tip-1-deploy-a-standalone-jvm-web-app-with-gradle-and-the-app-engine-plugin/</link><pubDate>Fri, 29 Nov 2019 16:49:31 +0100</pubDate><guid>https://glaforge.dev/posts/2019/11/29/serverless-tip-1-deploy-a-standalone-jvm-web-app-with-gradle-and-the-app-engine-plugin/</guid><description>&lt;p>Requirements:&lt;/p>
&lt;ul>
&lt;li>an existing Google Cloud Platform account and project&lt;/li>
&lt;li>a Java or alternative language web application&lt;/li>
&lt;li>a Gradle build that creates a standalone executable JAR file&lt;/li>
&lt;/ul>
&lt;p>In youd &lt;code>build.gradle&lt;/code> file, add the App Engine gradle plugin to your &lt;code>buildscript&lt;/code> dependencies:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-groovy" data-lang="groovy">&lt;span style="display:flex;">&lt;span>buildscript &lt;span style="color:#666">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>    repositories &lt;span style="color:#666">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>        jcenter&lt;span style="color:#666">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>        mavenCentral&lt;span style="color:#666">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>    &lt;span style="color:#666">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>    dependencies &lt;span style="color:#666">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>        classpath &lt;span style="color:#4070a0">&amp;#39;com.google.cloud.tools:appengine-gradle-plugin:2.+&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>    &lt;span style="color:#666">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#666">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Apply the plugin, to make use of it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-groovy" data-lang="groovy">&lt;span style="display:flex;">&lt;span>apply &lt;span style="color:#002070;font-weight:bold">plugin:&lt;/span> &lt;span style="color:#4070a0">&amp;#34;com.google.cloud.tools.appengine-appyaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then you can configure the &lt;code>appengine&lt;/code> task to point at the standalone executable JAR:&lt;/p></description><content:encoded>
<![CDATA[<p>Requirements:</p>
<ul>
<li>an existing Google Cloud Platform account and project</li>
<li>a Java or alternative language web application</li>
<li>a Gradle build that creates a standalone executable JAR file</li>
</ul>
<p>In youd <code>build.gradle</code> file, add the App Engine gradle plugin to your <code>buildscript</code> dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>buildscript <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    repositories <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        jcenter<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>        mavenCentral<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#39;com.google.cloud.tools:appengine-gradle-plugin:2.+&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Apply the plugin, to make use of it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#34;com.google.cloud.tools.appengine-appyaml&#34;</span>
</span></span></code></pre></div><p>Then you can configure the <code>appengine</code> task to point at the standalone executable JAR:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>appengine <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    stage<span style="color:#666">.</span><span style="color:#4070a0">artifact</span> <span style="color:#666">=</span> 
</span></span><span style="display:flex;"><span>            <span style="color:#4070a0">&#34;${buildDir}/libs/${project.name}-${project.version}.jar&#34;</span>
</span></span><span style="display:flex;"><span>    deploy <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        projectId <span style="color:#666">=</span> <span style="color:#4070a0">&#34;YOUR-PROJECT-ID&#34;</span>
</span></span><span style="display:flex;"><span>        version <span style="color:#666">=</span> <span style="color:#4070a0">&#34;1&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>You can customize the path of the artifact, specify the project ID outside, or define an App Engine version that&rsquo;s dependent on your project version, a git commit, etc.</p>
<p>Note that the App Engine gradle plugin expects to find the <code>app.yaml</code> configuration file in <code>src/main/appengine</code>.</p>
<p>You can then deploy your application with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ./gradlew appengineDeploy
</span></span></code></pre></div><p>More information:</p>
<ul>
<li><a href="https://cloud.google.com/appengine/docs/standard/java11/config/appref">app.yaml</a></li>
<li><a href="https://github.com/GoogleCloudPlatform/app-gradle-plugin">App Engine gradle plugin</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Implementing Webhooks, not as trivial as it may seem</title><link>https://glaforge.dev/talks/2019/11/25/implementing-webhooks-not-as-trivial-as-it-may-seem/</link><pubDate>Mon, 25 Nov 2019 16:58:28 +0100</pubDate><guid>https://glaforge.dev/talks/2019/11/25/implementing-webhooks-not-as-trivial-as-it-may-seem/</guid><description>&lt;p>You&amp;rsquo;ve certainly interacted with &lt;strong>webhooks&lt;/strong> at some point: with a Github commit webhook, for Slack or Dialogflow chatbots, for being notified of Stripe payments, or when you receive an SMS via Twilio. The concept is fairly well known, but there are some roadblocks along the way, whether you implement a webhook handler (the URL being called) or a webhook backend (the service notifying URLs). It&amp;rsquo;s not necessarily as trivial as it may first seem. As I&amp;rsquo;ve been interested in Web APIs for a long time, I decided to look into this topic a bit more, by working on a new talk.&lt;/p></description><content:encoded>
<![CDATA[<p>You&rsquo;ve certainly interacted with <strong>webhooks</strong> at some point: with a Github commit webhook, for Slack or Dialogflow chatbots, for being notified of Stripe payments, or when you receive an SMS via Twilio. The concept is fairly well known, but there are some roadblocks along the way, whether you implement a webhook handler (the URL being called) or a webhook backend (the service notifying URLs). It&rsquo;s not necessarily as trivial as it may first seem. As I&rsquo;ve been interested in Web APIs for a long time, I decided to look into this topic a bit more, by working on a new talk.</p>
<h2 id="videos">Videos</h2>
<p>I&rsquo;ve had the chance of giving this talk at GeeCON Prague:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/hRz38zGPSAU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>As well as (in French) at BDX.IO:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/DRf7-dmhNHA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>You can also watch the slide deck here:</p>
<script async class="speakerdeck-embed" data-id="b89328ff810c4955a8c2427d05f18bed" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="summary">Summary</h2>
<p>Initially, I was focusing on the backend webhook implementation aspects, but both the sending and receiving ends of webhooks have their own challenges.</p>
<p>Let me name the ones I encountered.</p>
<p>On the handler / client / receiving side, your webhook handlers should:</p>
<ul>
<li>Reply with a 200 HTTP status code, to let the service provider know that you successfully received the event notification.</li>
<li>Reply fast, so that the service provider doesn&rsquo;t have to keep as many open connections to the handlers, to let it scale more gracefully to more customers. So a good approach is to acknowledge the reception of the event, but treat that event asynchronously afterwards.</li>
<li>Ack reception and defer event handling, as mentioned above when replying fast, it&rsquo;s important to &ldquo;ack&rdquo; quickly the reception, and then you&rsquo;re free to do long event handling afterwards, potentially with some worker queue, with workers that can treat those events at their own pace. You can then scale your workers pool when you need to deal with more notifications.</li>
<li>Calls should be idempotent. Sometimes, for various reasons, it&rsquo;s possible you get event notifications twice for the same event.</li>
<li>Use IP whitelisting, when possible, to ensure that only some IP addresses can ping your handler. Since you&rsquo;re opening an URL to the public, better be sure that it&rsquo;s only the service provider that calls you. But it&rsquo;s not always possible to define such a whitelist, as IP addresses are not necessarily fixed for the service provider.</li>
<li>Check request signature, this time not to avoid a DDoS, but more to ensure the integrity of the event payload that you receive. More on signatures in the server part below.</li>
<li>Take advantage of serverless solutions, as sometimes, you don&rsquo;t get a frequent or regular flow of event notifications, why have a server running all the time? Instead, you can take advantage of serverless solutions, like <a href="https://cloud.google.com/appengine/">App Engine</a> or <a href="https://cloud.google.com/run/">Cloud Run</a>, as you&rsquo;re only billed for the time used.</li>
</ul>
<p>On the service provider / server / notifier side, your webhook backend should:</p>
<ul>
<li>Send small data payloads, instead of the whole resource that triggered the event. For instance, your service might notified handlers that there&rsquo;s a new video available. But you don&rsquo;t want to send tons of gigabytes of videos to each and every handler subscribed to that event. So just send a reference to that resource, and keep the event payload small.</li>
<li>Timeout if client is too slow, as you can&rsquo;t wait forever for a faulty handler to reply. Cut the connection if the client handler doesn&rsquo;t reply under a set time interval, and treat this as if the message hasn&rsquo;t been successfully delivered. Which means you&rsquo;ll have to retry sending that event later on.</li>
<li>Retry sending events with exponential backoff, to not overload a handler which is struggling to keep pace, in order to avoid DDoS-ing it with your retries. Instead, use exponential backoff to try to redeliver, for example, after 1s, 2s, 4s, 8s, 16s, etc. </li>
<li>Keep track of non-responding handlers, after too many failed delivery attempts, mark those handlers as non-responding, and perhaps somehow notify the creator of the handler that it&rsquo;s not responding correctly.</li>
<li>Deliver messages from a work queue, as you have potentially tons of subscribers interested in your events, you don&rsquo;t want your event loop to be taking longer and longer to operate as the number of handlers grow, and instead, offload the work to some worker queue that you can scale independently from the work of keeping pace with ongoing events flow.</li>
<li>Batch events when too frequent, when there are too many event notifications to send. It might be more costly to send each and every event as they come, in real-time. If there are too many events, you can group them in one batch, so as to deliver them at an increased interval of time to your handlers.</li>
<li>Use a dead letter queue, for auditing purpose in particular. For non-responding handlers, or in case of handlers sometimes miss some events, you can push those never-received events in a dead letter queue, so that later on handler developers can check it to see if they actually missed something at some point in the flow.</li>
<li>Use HTTPS for secured connections, well, everyone should use HTTPS all the time these days anyone, but it&rsquo;s better for avoiding man-in-the-middle attacks, to avoid events replay, etc.</li>
<li>Sign requests with a secret, when handlers and service providers share a common secret, the provider can signe the request it sends to handlers, so that handlers can check the message is really coming from the service provider. For example, the Github API is using an HMAC signature, with a SHA-1 digest.</li>
<li>Use proper authentication/authorization mechanisms. This one is a bit vague, but the usual authentication/authorization best practices still apply to webhooks!</li>
</ul>
<p>Going further, I&rsquo;d like to expand this presentation with more hands-on concrete demos, that put all those best practices into action, and perhaps create some animations to show what happens when handlers are flooded with notifications, when handlers don&rsquo;t respond rapidly enough, etc, as that would probably help visualise more concretely each of those problems or practices. Let&rsquo;s see how I can continue iterating and improving this presentation and topic!</p>
<p>Resources</p>
<p>Last but not least, there are some great resources available on the topic, that I&rsquo;ve added at the end of my slide deck. Be sure to check them out as well:</p>
<ul>
<li><a href="https://speakerdeck.com/apistrat/crafting-a-great-webhooks-experience-by-john-sheehan">Crafting a great webhooks experience</a> (John Sheehan)</li>
<li><a href="https://requestbin.com/blog/working-with-webhooks/">WebHooks: the definitive guide</a></li>
<li><a href="https://www.infoq.com/presentations/webhooks-api/">WebHooks: The API Strikes Back</a> (InfoQ)</li>
<li><a href="https://hackernoon.com/webhook-vs-api-whats-the-difference-8d41e6661652">Webhooks vs APIs</a></li>
<li><a href="https://www.programmableweb.com/news/what-webhooks-push-styled-api-and-how-does-it-work/analysis/The2017/03/28">What is a Webhooks push-style API &amp; how does it work</a> (ProgrammableWeb)</li>
<li><a href="https://restful.io/webhooks-dos-and-dont-s-what-we-learned-after-integrating-100-apis-d567405a3671">Webhooks do&rsquo;s &amp; dont&rsquo;s: what we learned after integration 100+ APIs</a></li>
<li><a href="https://www.programmableweb.com/news/what-are-webhooks-and-how-do-they-enable-real-time-web/2012/01/30">How &amp; why Pusher adopted Webhooks</a></li>
<li><a href="https://nordicapis.com/webhooks-vs-websub-which-one-is-better-to-stream-your-events-in-real-time/">Webhooks vs WebSub: Which Is Better For Real-Time Event Streaming?</a></li>
<li><a href="https://techblog.commercetools.com/webhooks-the-devil-in-the-details-ca7f7982c24f">Webhooks, the devil is in the details</a></li>
<li><a href="https://phalt.github.io/webhooks-in-apis/">How to design a webhook for my API</a></li>
<li><a href="https://tomasz.janczuk.org/2018/03/serverless-webhooks-to-revolutionize-the-saas.html">Serverless webhooks to revolutionize the SaaS</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>App Engine 2nd generation runtimes and serverless containers with Cloud Run at Cloud Next Tokyo</title><link>https://glaforge.dev/talks/2019/08/08/app-engine-2nd-generation-runtimes-and-serverless-containers-with-cloud-run-at-cloud-next-tokyo/</link><pubDate>Thu, 08 Aug 2019 17:04:06 +0100</pubDate><guid>https://glaforge.dev/talks/2019/08/08/app-engine-2nd-generation-runtimes-and-serverless-containers-with-cloud-run-at-cloud-next-tokyo/</guid><description>&lt;p>Last week, I was in Tokyo for the first time, to speak at the Google Cloud Next conference. During the DevDay, I spoke about Google App Engine and its 2nd generation runtimes, and I also presented Cloud Run on how to deploy and run containers in a serverless fashion. It&amp;rsquo;s been awesome to visit Japan for the first time and get a chance to meet developers there. Here are the slides I presented:&lt;/p></description><content:encoded>
<![CDATA[<p>Last week, I was in Tokyo for the first time, to speak at the Google Cloud Next conference. During the DevDay, I spoke about Google App Engine and its 2nd generation runtimes, and I also presented Cloud Run on how to deploy and run containers in a serverless fashion. It&rsquo;s been awesome to visit Japan for the first time and get a chance to meet developers there. Here are the slides I presented:</p>
<h2 id="app-engine-2nd-generation-runtimes">App Engine 2nd generation runtimes</h2>
<script async class="speakerdeck-embed" data-id="675bbf764343407d92eb4c2d9618de5c" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="serverless-containers-with-cloud-run">Serverless containers with Cloud Run</h2>
<script async class="speakerdeck-embed" data-id="59137c528fc04162a7806605fd936b4e" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Getting started with Micronaut on Google App Engine Java 11</title><link>https://glaforge.dev/posts/2019/07/04/getting-started-with-micronaut-on-google-app-engine-java-11/</link><pubDate>Thu, 04 Jul 2019 17:07:02 +0100</pubDate><guid>https://glaforge.dev/posts/2019/07/04/getting-started-with-micronaut-on-google-app-engine-java-11/</guid><description>&lt;p>A &lt;a href="https://cloud.google.com/blog/products/application-development/turn-it-up-to-eleven-java-11-runtime-comes-to-app-engine">new Java runtime was announced for Google App Engine&lt;/a> standard: with Java 11. It&amp;rsquo;s currently in beta, but anybody can already try it out. Another interesting announcement was the fact that the instances running your apps now get &lt;a href="https://cloud.google.com/blog/products/application-development/app-engine-second-generation-runtimes-now-get-double-the-memory-plus-go-112-and-php-73-now-generally-available">double the memory&lt;/a>! So with this double dose of great news, I decided to craft a little tutorial to show how to deploy a Micronaut application on App Engine Java 11. And because Apache Groovy is, well, groovy, I&amp;rsquo;ll go ahead and use Groovy for my programming language, but of course, the same steps apply to Java workloads as well.&lt;/p></description><content:encoded>
<![CDATA[<p>A <a href="https://cloud.google.com/blog/products/application-development/turn-it-up-to-eleven-java-11-runtime-comes-to-app-engine">new Java runtime was announced for Google App Engine</a> standard: with Java 11. It&rsquo;s currently in beta, but anybody can already try it out. Another interesting announcement was the fact that the instances running your apps now get <a href="https://cloud.google.com/blog/products/application-development/app-engine-second-generation-runtimes-now-get-double-the-memory-plus-go-112-and-php-73-now-generally-available">double the memory</a>! So with this double dose of great news, I decided to craft a little tutorial to show how to deploy a Micronaut application on App Engine Java 11. And because Apache Groovy is, well, groovy, I&rsquo;ll go ahead and use Groovy for my programming language, but of course, the same steps apply to Java workloads as well.</p>
<h2 id="getting-started-on-google-cloud-platform">Getting started on Google Cloud Platform</h2>
<p>In this article, I assume you&rsquo;ve created an account on Google Cloud Platform already (follow the &ldquo;getting started&rdquo; blue buttons to create an account otherwise and benefit from the <a href="https://cloud.google.com/free/">free tier and free quota</a>), and that you&rsquo;ve downloaded and installed the <a href="https://cloud.google.com/sdk/gcloud/">gcloud</a> command-line SDK. You&rsquo;ll be able to follow the first few steps in the <a href="https://cloud.google.com/appengine/docs/standard/java11/quickstart">quickstart guide</a>, to create your GCP project and make it ready for using App Engine, in particular those commands:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud projects create mn113-gae-java11 --set-as-default
</span></span></code></pre></div><p>You&rsquo;ll have to change the project ID from <code>&quot;mn113-gae-java11&quot;</code> to your own name of choice.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud app create --project<span style="color:#666">=</span>mn113-gae-java11
</span></span></code></pre></div><p>It&rsquo;ll ask for a cloud region to use, I&rsquo;ve decided to go with europe-west for this one.</p>
<p>The above steps can as well be done from the cloud console UI as well, at <a href="https://console.cloud.google.com/">https://console.cloud.google.com</a>.</p>
<p>Although your application will run for free within the free quota, we need to enable billing for our app, as it&rsquo;s going to use Cloud Build to build our app, and the latter requires billing to be enabled.</p>
<p>To enable billing and the Cloud Build API, please follow the first step of the <a href="https://cloud.google.com/appengine/docs/standard/java11/quickstart">quickstart guide</a> mentioned above.</p>
<h2 id="building-our-micronaut-application">Building our Micronaut application</h2>
<p>Time to fire the Micronaut goodness! On my machine, I&rsquo;m using SDKman to install my SDKs, so I&rsquo;ve installed Java 11 and Micronaut 1.1.3 as explained in <a href="https://docs.micronaut.io/latest/guide/index.html#buildCLI">Micronaut&rsquo;s getting started guide</a>.</p>
<p>Our first step will be to create our basic Micronaut application, thanks to the following command, with the mn command-line SDK:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ mn create-app mn113-gae-java11 --lang<span style="color:#666">=</span>groovy
</span></span></code></pre></div><p>The structure of your Micronaut project is created, with a Gradle-based build, an Application main class, an <code>application.yml</code> file to configure your application.</p>
<p>As this application isn&rsquo;t yet doing anything useful, we&rsquo;re create a <code>&quot;Hello World&quot;</code> controller with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ mn create-controller hello
</span></span></code></pre></div><p>We&rsquo;ll modify this newly created HelloController.groovy controller as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span> mn113<span style="color:#666">.</span><span style="color:#4070a0">gae</span><span style="color:#666">.</span><span style="color:#4070a0">java11</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">io.micronaut.http.annotation.*</span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Controller</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;/hello&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">HelloController</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#555;font-weight:bold">@Get</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;/&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    String <span style="color:#06287e">index</span><span style="color:#666">()</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#007020;font-weight:bold">return</span> <span style="color:#4070a0">&#34;Hello Micronaut!&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>|</p>
<p>On the <code>/hello</code> path, we&rsquo;ll simply return a plain text response showing our greeting message.</p>
<p>To run your application locally, to check everything is working fine, you&rsquo;ll simply run:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ./gradlew run
</span></span></code></pre></div><p>And you can check that <code>localhost:8080/hello</code> returns the <code>Hello Micronaut</code> message. So far so good.</p>
<h2 id="configure-our-micronaut-application-for-app-engine">Configure our Micronaut application for App Engine</h2>
<p>In order to deploy the App Engine, we&rsquo;ll use the App Engine Gradle plugin. So we need to amend our <code>build.gradle</code> a little.</p>
<p>Let&rsquo;s define where Gradle will find the plugin:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>buildscript <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    repositories <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        jcenter<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>        mavenCentral<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#39;com.google.cloud.tools:appengine-gradle-plugin:2.+&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>We&rsquo;ll make use of the plugin:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#34;com.google.cloud.tools.appengine-appyaml&#34;</span>
</span></span></code></pre></div><p>Let&rsquo;s configure the App Engine section:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>appengine <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    stage<span style="color:#666">.</span><span style="color:#4070a0">artifact</span> <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>            <span style="color:#4070a0">&#34;${buildDir}/libs/${project.name}-${project.version}.jar&#34;</span>
</span></span><span style="display:flex;"><span>    deploy <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        projectId <span style="color:#666">=</span> <span style="color:#4070a0">&#34;mn113-gae-java11&#34;</span>
</span></span><span style="display:flex;"><span>        version <span style="color:#666">=</span> <span style="color:#4070a0">&#34;1&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Note that App Engine&rsquo;s version string is not supporting dots or underscores (only alphanumeric characters), hence why I replaced the <code>version</code> property. Furthermore a reported <a href="https://github.com/GoogleCloudPlatform/app-gradle-plugin/issues/353">issue</a> prevents me from reusing the Gradle project&rsquo;s own project property in the <code>projectId</code> property.</p>
<h2 id="configure-the-app-engine-deployment">Configure the App Engine deployment</h2>
<p>App Engine has its own deployment configuration file, where you will define the App Engine runtime (in our case Java 11), and you can also decide what kind of instance will be used to run your code. Last but not least, you can customize the entry point which defines how your application should be started.</p>
<p>In <code>src/main/appengine</code> we&rsquo;ll add a file named app.yaml:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">runtime</span>:<span style="color:#bbb"> </span>java11<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">instance_class</span>:<span style="color:#bbb"> </span>F4<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">entrypoint</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;java -agentpath:/opt/cdbg/cdbg_java_agent.so=--log_dir=/var/log -jar mn113-gae-java11-0.1.jar&#39;</span><span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="deploying-to-app-engine">Deploying to App Engine</h2>
<p>Now you&rsquo;re ready to deploy your Micronaut application on App Engine&rsquo;s Java 11 runtime!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ./gradlew appengineDeploy
</span></span></code></pre></div><p>After a minute or so, and if billing and the Cloud Build API are enabled as said in the introduction, your Micronaut app should be deployed! You can then browse <a href="https://mn113-gae-java11.appspot.com/hello">https://mn113-gae-java11.appspot.com/hello</a> and get your Hello Micronaut greeting.</p>
<h2 id="whats-next">What&rsquo;s next</h2>
<p>In upcoming articles, I&rsquo;ll cover some other aspects, like how to configure and optimize static asset serving, or perhaps how to integrate with databases or other services of Google Cloud Platform. So stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Update on the recent serverless developments on GCP at DataXDay 2019</title><link>https://glaforge.dev/talks/2019/07/02/update-on-the-recent-serverless-developments-on-gcp-at-dataxday-2019/</link><pubDate>Tue, 02 Jul 2019 17:19:51 +0100</pubDate><guid>https://glaforge.dev/talks/2019/07/02/update-on-the-recent-serverless-developments-on-gcp-at-dataxday-2019/</guid><description>&lt;p>At &lt;a href="https://dataxday.fr/">DataXDay 2019&lt;/a>, last week, I had the chance to present an updated version of my introductory talk on the &lt;a href="https://cloud.google.com/serverless/">serverless compute options&lt;/a> on Google Cloud Platform. There&amp;rsquo;s always something new to cover!&lt;/p>
&lt;p>For instance, if I put my Java Champion hat on, I&amp;rsquo;d like to mention that there are new runtimes for App Engine standard, like the beta for Java 11, and there&amp;rsquo;s twice the amount of memory as before. On Cloud Functions, we have an alpha for Java as well (currently Java 8, but it&amp;rsquo;ll be soon moved to Java 11 instead, as customers are more interested in the latest LTS version)&lt;/p></description><content:encoded>
<![CDATA[<p>At <a href="https://dataxday.fr/">DataXDay 2019</a>, last week, I had the chance to present an updated version of my introductory talk on the <a href="https://cloud.google.com/serverless/">serverless compute options</a> on Google Cloud Platform. There&rsquo;s always something new to cover!</p>
<p>For instance, if I put my Java Champion hat on, I&rsquo;d like to mention that there are new runtimes for App Engine standard, like the beta for Java 11, and there&rsquo;s twice the amount of memory as before. On Cloud Functions, we have an alpha for Java as well (currently Java 8, but it&rsquo;ll be soon moved to Java 11 instead, as customers are more interested in the latest LTS version)</p>
<p>In this talk, I also covered <a href="http://cloud.run/">Cloud Run</a>, and Cloud Run on GKE (Google Kubernetes Engine), as well as telling a few words about the <a href="https://knative.dev/">Knative</a> open source building blocks for Kubernetes, which allows to create serverless portable containers.</p>
<p>Here&rsquo;s the slide deck I presented at the conference:</p>
<script async class="speakerdeck-embed" data-id="620a008ef3694e93b3a43d15583ff980" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Turn it up to eleven: Java 11 runtime comes to App Engine</title><link>https://glaforge.dev/posts/2019/06/21/turn-it-up-to-eleven-java-11-runtime-comes-to-app-engine/</link><pubDate>Fri, 21 Jun 2019 19:00:26 +0100</pubDate><guid>https://glaforge.dev/posts/2019/06/21/turn-it-up-to-eleven-java-11-runtime-comes-to-app-engine/</guid><description>&lt;p>Yesterday, we &lt;a href="https://cloud.google.com/blog/products/application-development/app-engine-second-generation-runtimes-now-get-double-the-memory-plus-go-112-and-php-73-now-generally-available">announced&lt;/a> new second-generation runtimes for Go 1.12 and PHP 7.3. In addition, App Engine standard instances now run with double the memory. Today, we&amp;rsquo;re happy to announce the availability of the new Java 11 second-generation runtime for App Engine standard in beta. Now, you can take advantage of the latest &lt;a href="https://www.oracle.com/technetwork/java/java-se-support-roadmap.html">Long-Term-Support version&lt;/a> of the Java programming language to develop and deploy your applications on our fully-managed serverless application platform.&lt;/p>
&lt;p>Based on technology from the &lt;a href="https://github.com/google/gvisor">gVisor container sandbox&lt;/a>, second-generation runtimes let you write portable web apps and microservices that take advantage of App Engine&amp;rsquo;s unique auto-scaling, built-in security and pay-per-use billing model&amp;mdash;without some of App Engine&amp;rsquo;s earlier runtime restrictions. Second generation-runtimes also let you build applications more idiomatically. You&amp;rsquo;re free to use whichever framework or library you need for your project&amp;mdash;there are no limitations in terms of what classes you can use, for instance. You can even use native dependencies if needed. Beyond Java, you can also use alternative JVM (Java Virtual Machine) languages like &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a>, &lt;a href="https://kotlinlang.org/">Kotlin&lt;/a> or &lt;a href="https://www.scala-lang.org/">Scala&lt;/a> if you wish.&lt;/p></description><content:encoded>
<![CDATA[<p>Yesterday, we <a href="https://cloud.google.com/blog/products/application-development/app-engine-second-generation-runtimes-now-get-double-the-memory-plus-go-112-and-php-73-now-generally-available">announced</a> new second-generation runtimes for Go 1.12 and PHP 7.3. In addition, App Engine standard instances now run with double the memory. Today, we&rsquo;re happy to announce the availability of the new Java 11 second-generation runtime for App Engine standard in beta. Now, you can take advantage of the latest <a href="https://www.oracle.com/technetwork/java/java-se-support-roadmap.html">Long-Term-Support version</a> of the Java programming language to develop and deploy your applications on our fully-managed serverless application platform.</p>
<p>Based on technology from the <a href="https://github.com/google/gvisor">gVisor container sandbox</a>, second-generation runtimes let you write portable web apps and microservices that take advantage of App Engine&rsquo;s unique auto-scaling, built-in security and pay-per-use billing model&mdash;without some of App Engine&rsquo;s earlier runtime restrictions. Second generation-runtimes also let you build applications more idiomatically. You&rsquo;re free to use whichever framework or library you need for your project&mdash;there are no limitations in terms of what classes you can use, for instance. You can even use native dependencies if needed. Beyond Java, you can also use alternative JVM (Java Virtual Machine) languages like <a href="http://groovy-lang.org/">Apache Groovy</a>, <a href="https://kotlinlang.org/">Kotlin</a> or <a href="https://www.scala-lang.org/">Scala</a> if you wish.</p>
<p>In addition to more developer freedom, you also get all the benefits of a serverless approach. App Engine can transparently scale your app up to n and back down to 0, so your application can handle the load when it&rsquo;s featured on primetime TV or goes viral on social networks. Likewise, it scales to zero if no traffic comes. Your bill will also be proportional to your usage, so if nobody uses your app, you won&rsquo;t pay a dime (there is also a free tier available).</p>
<p>App Engine second-generation runtimes also mean you don&rsquo;t need to worry about security tasks like applying OS security patches and updates. Your code runs securely in a <a href="https://gvisor.dev/">gVisor</a>-based sandbox, and we update the underlying layers for you. No need to provision or manage servers yourself&mdash;just focus on your code and your ideas!</p>
<h2 id="whats-new">What&rsquo;s new?</h2>
<p>When you migrate to Java 11, you gain access to all the goodies of the most recent Java versions: you can now use advanced type inference with the new var keyword, create lists or maps easily and concisely with the new immutable collections, and simplify calling remote hosts thanks to the graduated HttpClient support. Last but not least, you can also use the JPMS module system introduced in Java 9.</p>
<p>You&rsquo;ll also find some changes in the Java 11 runtime. For example, the Java 11 runtime does not provide a Servlet-based runtime anymore. Instead, you need to bundle a server with your application in the form of an executable JAR. This means that you are free to choose whichever library or framework you want, be it based on the Servlet API or other networking stacks like the Netty library. In other words, feel free to use <a href="https://spring.io/projects/spring-boot">Spring Boot</a>, <a href="https://vertx.io/">Vert.x</a>, <a href="http://sparkjava.com/">SparkJava</a>, <a href="https://ktor.io/">Ktor</a>, <a href="https://helidon.io/#/">Helidon</a> or <a href="https://micronaut.io/">Micronaut</a> if you wish!</p>
<p>Last but not least, second-generation runtimes don&rsquo;t come with the built-in APIs like Datastore or memcache from the App Engine SDK. Instead, you can use the standalone services with their Google Cloud client libraries, or use other similar services of your choice. Be sure to look into our <a href="https://cloud.google.com/appengine/docs/standard/java11/java-differences">migration guide</a> for more help on these moves.</p>
<h2 id="getting-started">Getting started</h2>
<p>To deploy to App Engine Java 11, all you need is an app.yaml file where you specify <code>runtime: java11</code>, signifying that your application should use Java 11. That&rsquo;s enough to tell App Engine to use the Java 11 runtime, regardless of whether you&rsquo;re using an executable JAR, or a WAR file with a provided servlet-container. However, the new runtime also gives you more control on how your application starts: by specifying an extra <code>entrypoint</code> parameter in <code>app.yaml</code>, you can then customize the java command flags, like the -X memory settings.</p>
<p>With Java 11, the <code>java</code> command now includes the ability to run single independent <code>*.java</code> files without compiling them with <code>javac</code>! For this short getting started section, we are going to use it to run the simplest hello world example with the JDK&rsquo;s built-in HTTP server:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">com.sun.net.httpserver.HttpServer</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.io.*</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">import</span><span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">java.net.InetSocketAddress</span>;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">Main</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">public</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">static</span><span style="color:#bbb"> </span><span style="color:#902000">void</span><span style="color:#bbb"> </span><span style="color:#06287e">main</span>(String<span style="color:#666">[]</span><span style="color:#bbb"> </span>args)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">throws</span><span style="color:#bbb"> </span>IOException<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>server<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>HttpServer.<span style="color:#4070a0">create</span>(<span style="color:#007020;font-weight:bold">new</span><span style="color:#bbb"> </span>InetSocketAddress(8080),<span style="color:#bbb"> </span>0);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>server.<span style="color:#4070a0">createContext</span>(<span style="color:#4070a0">&#34;/&#34;</span>,<span style="color:#bbb"> </span>t<span style="color:#bbb"> </span><span style="color:#666">-&gt;</span><span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>response<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Hello World from Java 11.&#34;</span>.<span style="color:#4070a0">getBytes</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>t.<span style="color:#4070a0">sendResponseHeaders</span>(200,<span style="color:#bbb"> </span>response.<span style="color:#4070a0">length</span>);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#007020;font-weight:bold">try</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">var</span><span style="color:#bbb"> </span>os<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span>t.<span style="color:#4070a0">getResponseBody</span>())<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">                </span>os.<span style="color:#4070a0">write</span>(response);<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>});<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>server.<span style="color:#4070a0">start</span>();<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notice how our Main class uses the var keyword introduced in Java 10, and how we re-used the keyword again in the <code>try-</code>with-resources block, as <code>Java 11</code> makes possible.</p>
<p>Now it&rsquo;s time to prepare our <code>app.yaml</code> file. First, specify the <code>java11</code> runtime. In addition, <code>entrypoint</code> define the actual <code>java</code> command with which to we&rsquo;ll be running to launch the server. The <code>java</code> command points at our single Java source file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">runtime</span>:<span style="color:#bbb"> </span>java11<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">entrypoint</span>:<span style="color:#bbb"> </span>java Main.java<span style="color:#bbb">
</span></span></span></code></pre></div><p>Finally, don&rsquo;t forget to deploy your application with the <code>gcloud app deploy app.yaml</code> command. Of course, you can also take advantage of dedicated Maven and Gradle plugins for your deployments.</p>
<h2 id="try-java-11-on-app-engine-standard-today">Try Java 11 on App Engine standard today</h2>
<p>You can write your App Engine applications with Java 11 today, thanks to the newly released runtime in beta. Please read the documentation to get started and learn more about it, have a look at the <a href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/appengine-java11">many samples</a> that are available, and check out the <a href="https://cloud.google.com/appengine/docs/standard/java11/java-differences">migration guide</a> on moving from Java 8 to 11. And don&rsquo;t forget you can take advantage of the <a href="https://cloud.google.com/free/docs/always-free-usage-limits">App Engine free tier</a> while you experiment with our platform.</p>
<p><em>From the App Engine Java 11 team Ludovic Champenois, Eamonn McManus, Ray Tsang, Alexis Moussine-Pouchkine, Averi Kitsch, Lawrence Latif, Angela Funk.</em></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A serverless Java developer's journey</title><link>https://glaforge.dev/talks/2019/04/17/a-serverless-java-developer-journey/</link><pubDate>Wed, 17 Apr 2019 17:22:02 +0100</pubDate><guid>https://glaforge.dev/talks/2019/04/17/a-serverless-java-developer-journey/</guid><description>&lt;p>Last week at the Google &lt;a href="https://cloud.withgoogle.com/next/sf/">Cloud Next&lt;/a> conference, I had the chance to speak about the Java developer&amp;rsquo;s journey through the &amp;ldquo;serverless&amp;rdquo; offering of &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>, with my colleague Vinod Ramachandran (Product Manager on some of our serverless products):&lt;/p>
&lt;p>Serverless Java in 2019 is going to be ubiquitous in your favorite cloud. Well, it&amp;rsquo;s actually been 10 years since you could take advantage of Java on Google App Engine. But now you can run your apps on the brand-new Java 11 runtime. Not only servlet-based apps but also executable JARs. And what about authoring functions? Until now, you could only use Node or Python, but today, Java is the third runtime available for Google Cloud Functions. We will review the various ways you can develop your Java functions. Last but not least, thanks to serverless containers, containerized Java workloads run serverlessly, without you caring for infrastructure, scaling, or paying for idle machines. Through various demos, we will look at the many ways Java developers will be able to write, build, test, and deploy code in Java on the rich serverless offering of Google Cloud Platform.&lt;/p></description><content:encoded>
<![CDATA[<p>Last week at the Google <a href="https://cloud.withgoogle.com/next/sf/">Cloud Next</a> conference, I had the chance to speak about the Java developer&rsquo;s journey through the &ldquo;serverless&rdquo; offering of <a href="https://cloud.google.com/">Google Cloud Platform</a>, with my colleague Vinod Ramachandran (Product Manager on some of our serverless products):</p>
<p>Serverless Java in 2019 is going to be ubiquitous in your favorite cloud. Well, it&rsquo;s actually been 10 years since you could take advantage of Java on Google App Engine. But now you can run your apps on the brand-new Java 11 runtime. Not only servlet-based apps but also executable JARs. And what about authoring functions? Until now, you could only use Node or Python, but today, Java is the third runtime available for Google Cloud Functions. We will review the various ways you can develop your Java functions. Last but not least, thanks to serverless containers, containerized Java workloads run serverlessly, without you caring for infrastructure, scaling, or paying for idle machines. Through various demos, we will look at the many ways Java developers will be able to write, build, test, and deploy code in Java on the rich serverless offering of Google Cloud Platform.</p>
<p>Until fairly recently, our compute serverless products consisted only of <a href="https://cloud.google.com/appengine/">Google App Engine</a> for deploying apps and services, and <a href="https://cloud.google.com/functions/">Cloud Functions</a> for deploying functions. Furthermore, for the Java developer, the situation wasn&rsquo;t that great as Cloud Functions wasn&rsquo;t offering any Java support (only Node, Python and Go runtimes), and only App Engine provided a Java 8 runtime.</p>
<p>Fortunately, some very important announcements were made at Cloud Next:</p>
<ul>
<li>
<p>First of all, in addition to the Java 8 runtime, we have launched an alpha for a brand <a href="https://docs.google.com/forms/d/e/1FAIpQLSf5uE5eknJjFEmcVBI6sMitBU0QQ1LX_J7VrA_OTQabo6EEEw/viewform">new App Engine Java 11 runtime</a>.</p>
</li>
<li>
<p>We introduced a <a href="https://docs.google.com/forms/d/e/1FAIpQLScC98jGi7CfG0n3UYlj7Xad8XScvZC8-BBOg7Pk3uSZx_2cdQ/viewform">Java 8 flavor for Cloud Functions</a>.</p>
</li>
<li>
<p>Last but not least, we launched a new product, <a href="https://cloud.google.com/run/">Cloud Run</a>, which allows you to run containers serverlessly, and thus any Java workload that can be containerized.</p>
</li>
</ul>
<p>So you can develop Java functions, Java apps and Java-powered containers in a serverless fashion:</p>
<ul>
<li>Scaling on demand as needed to serve incoming requests as well as down to zero when no traffic comes.</li>
<li>Paying proportionally to the usage.</li>
<li>And all of that, without having to worry with server or cluster provisioning and management.</li>
</ul>
<p>Without further ado, let me share with you the video and the slides of this presentation:</p>
<p>Video recording:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/WnhAYX1Phxw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Now it&rsquo;s your turn! If you want to try out functions, apps, and containers in Java, here are a few pointers to get you started:</p>
<ul>
<li><a href="https://docs.google.com/forms/d/e/1FAIpQLSf5uE5eknJjFEmcVBI6sMitBU0QQ1LX_J7VrA_OTQabo6EEEw/viewform">Sign-up form</a> for App Engine Java 11</li>
<li><a href="https://docs.google.com/forms/d/e/1FAIpQLScC98jGi7CfG0n3UYlj7Xad8XScvZC8-BBOg7Pk3uSZx_2cdQ/viewform">Sign-up form</a> for Cloud Functions Java 8</li>
<li>Cloud Run <a href="https://cloud.google.com/run/">documentation</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>On curiosity and sharing with the world</title><link>https://glaforge.dev/posts/2019/02/15/on-curiosity-and-sharing-with-the-world/</link><pubDate>Fri, 15 Feb 2019 17:28:04 +0100</pubDate><guid>https://glaforge.dev/posts/2019/02/15/on-curiosity-and-sharing-with-the-world/</guid><description>&lt;p>At the end of December, I was contacted by someone I didn&amp;rsquo;t know, who asked me some interesting questions, and that led me to quite a bit of introspection.&lt;/p>
&lt;p>As a Java Champion and with your career history. I wanted to ask you what you consider are the most important skills for a Java programmer to have in their toolbox, especially a Senior Java programmer? Or maybe even a better question is what skills you developed that helped you become the Java Developer/Groovy Language Developer that you are today.&lt;/p></description><content:encoded>
<![CDATA[<p>At the end of December, I was contacted by someone I didn&rsquo;t know, who asked me some interesting questions, and that led me to quite a bit of introspection.</p>
<p>As a Java Champion and with your career history. I wanted to ask you what you consider are the most important skills for a Java programmer to have in their toolbox, especially a Senior Java programmer? Or maybe even a better question is what skills you developed that helped you become the Java Developer/Groovy Language Developer that you are today.</p>
<p>In a nutshell, as I answered this person, for me it all boiled down to lots of curiosity, and the desire to share my findings with the world. It&rsquo;s not really about knowing specific methodologies, technologies or languages, or which soft or hard skills to master. It&rsquo;s about the core attitudes from which all the rest will derive from. But first, a bit of background about me.</p>
<h2 id="a-bit-of-history">A bit of history</h2>
<p>Alright, so if I was contacted (and actually a few others as well) with those questions, it&rsquo;s because I&rsquo;m considered to be a visible and public person. Because I&rsquo;m known for my work in the Java community and more precisely in the Apache Groovy ecosystem. I&rsquo;ve been in the field for quite a number of years, along with my contributions in Open Source, and that makes me a senior developer. But how did I get there?</p>
<p>You&rsquo;ve learned a lot during your studies, but often, not much of what you learned is immediately applicable in your daily duties and tasks. So there&rsquo;s even more to learn to become a productive developer. I started working as a junior Java developer in 2001. I was lucky to have had a great mentor that helped me design and write better code. I also spent quite some time reading Java and development related news websites or blogs. I wanted to know what were the latest trends (new language features, frameworks), the best tools for the job, how developers were developing on their projects. So clearly, I was pretty curious to look beyond just what I was doing at work, but to see if I could become a better programmer by learning from others. There&rsquo;s so much great content on the web, so much information that is shared, from best practices to bug fixes explanations, that you can learn a lot. That&rsquo;s also more or less when I started blogging. I saw so many useful blog posts that helped me, that I thought it would be a good thing to share back things I learned that could be helpful to others as well.</p>
<p>In 2003, at work, I needed a way to extend an app I was working on, and clearly, some kind of scripting solution was what would allow the end-users of our app to further customize and tailor the application to their needs. So I spent some time reviewing existing Java scripting solutions, but none were really ideal. Fortunately, that&rsquo;s when Groovy was born. It was embryonic, and not really ready for prime time though. But it was what I needed.</p>
<p>I started playing with Groovy, but quickly I encountered tons of problems and bugs. Since the code was Open Source, I started looking at its codebase, outside of work. I quickly understood where some of the bugs were coming from, and found ways to fix them. Since the community was pretty open, I participated in the mailing-lists to tell about those bugs, to help other users. It was nice to feel being part of a nice, friendly and helpful community.</p>
<p>I used the bug tracker to file bugs and feature requests, and when I could I even submitted some patches to fix these. My patches were accepted, and in a handful of months, I was asked to become an official committer on the project (which I gladly accepted). By working with the other committers, I learned a lot about Java, the JVM, or how open source projects worked. That was super interesting. Since the code was public, I really wanted all my contributions to be top-notch, perfectly well tested and commented. Somehow I had the impression that the scrutiny of my peers mandated that I had to produce even better code than at work! So I perfected my craft. A lot.</p>
<p>Since I had already started sharing my findings on my blog (and later on on social networks), I became part of the so-called &ldquo;blogosphere&rdquo;, and started interacting with other bloggers. I wrote about Java and Groovy, of course, but the discussions with other open source developers, allowed me to also meet them in the real world. We even started a meetup of open source developers that shared what they were working on. That&rsquo;s how I did my first public presentation, to show Groovy to my peers, in 2004 or so, at our local gatherings. I came to know people working for big companies like Sun or Oracle, as well as smaller actors, from freelancers, to entrepreneurs. A handful of those companies started using Groovy, and that&rsquo;s how one day, someone asked if I&rsquo;d be ready to talk with them at a big conference. That was for JavaOne! My first big conference and presentation was in the US in front of 600 persons. Woh&hellip; That&rsquo;s how I started sharing more widely with the world, and also started travelling to spread the word.</p>
<p>I spent a lot of time on Groovy and its ecosystem, and I later got the chance to both work on those technologies for a living (after doing quite a bit of consulting), as well as even creating my own company to focus on the project. At the same time, I was still continuing presenting about Groovy, and still improving the language thanks to the feedback I was getting from the many developers I was meeting all around the world. I was doing developer advocacy at the same time as product management and development. Three hats in one. And by doing developer advocacy, that&rsquo;s also what landed me my current job of developer advocate at Google.</p>
<h2 id="the-ever-changing-nature-of-our-field">The ever changing nature of our field</h2>
<p>From the narrated history above, there&rsquo;s a theme that emerges: curiosity. But what lead me to being curious? Tons of people are doing 9-to-5 jobs, and that&rsquo;s totally fine. However, as we spend so much time in our lives at work, for me, it had to be interesting and motivating. To be interesting, work has to be somehow entertaining &mdash; beside spending quality time with great coworkers. If it&rsquo;s not interesting, you get bored very easily, and you don&rsquo;t want to wake up every morning to go to the office. So how not to be bored? By making your job more interesting. How to make it more interesting? Well, if you&rsquo;re passionate about what you&rsquo;re doing, it&rsquo;s much easier to go through the day and do fun and interesting things, event for a project that could appear as not being very fancy.</p>
<p>Programming was first a hobby, for me, as a child and teenager. It never really occurred to me it could become my job. Initially, I just wanted to be&hellip; an &ldquo;engineer&rdquo;. Perhaps in aerospace, or something like this. Who hasn&rsquo;t dreamt of becoming an astronaut? It&rsquo;s only late in my studies that I thought I could actually become a developer. So my hobby, my passion, became my job. But there&rsquo;s a big difference between working on stuff you want, versus being asked to work on stuff for the company which hired you. In order to not be bored, be sure to push for improving the project in interesting ways both for you and the end-users. If possible, perhaps try to introduce and learn new technologies that can make the product better, and at the same time make you learn something new. Be passionate about improving both your projects and your skills.</p>
<p>Notice also that in our field, we actually don&rsquo;t really have a choice but to learn. When I was a student, my current job didn&rsquo;t even exist. When I started working, the languages or tools I&rsquo;m using today weren&rsquo;t available then yet. So in IT, in programming, etc, there&rsquo;s always a new language, a new tool, a new practice, new patterns, etc, that come to light. It&rsquo;s a field where we have to be in a constant learning state, in order to stay relevant. If you&rsquo;re not learning, your skills will rot, you&rsquo;ll be less employable, you&rsquo;ll diminish your chances of having a fantastic job. So you have to be curious and learn all the time. To not be bored, but also to get better at your craft.</p>
<p>With all those new tools, languages, frameworks, technologies, you have to keep up with what&rsquo;s going on. You have to be ready to learn something new.</p>
<h2 id="sharing-is-caring">Sharing is caring</h2>
<p>We talked a lot about being curious, about learning all along, but I also mentioned about sharing. As the saying goes, sharing is caring, but it&rsquo;s also about creating opportunities for you.</p>
<p>Sharing what I learned or worked on was helpful for others too (who encountered similar problems, for example), but it&rsquo;s also how I came to meet wonderful people along the way. Even mentors and role models. If I hadn&rsquo;t blogged or tweeted, I wouldn&rsquo;t have been able to start making presentations at meetups and conferences. And many of the friends I have today are friends I met along the way, at meetups, conferences, working on open source projects together, and so on.</p>
<p>Without sharing my code, I wouldn&rsquo;t have had the opportunity to meet my future employers and colleagues, as well as the co-founders of my own startup. Sharing is great to be visible, of course, but it&rsquo;s a wonderful way to meet new people from whom you&rsquo;ll learn a lot.</p>
<p>Open source is sharing too. Working on open source projects, nurturing communities and ecosystems around those, further allowed me to meet great people around the world. And it&rsquo;s what lead me to get the jobs at companies I was interested in. It created great professional opportunities.</p>
<h2 id="summary">Summary</h2>
<p>Let&rsquo;s try to wrap up a bit. It&rsquo;s really not about learning a particular tool or technology. It&rsquo;s all about being curious and passionate about your craft, and to share what you&rsquo;ve learned with the world.</p>
<h3 id="be-curious">Be curious!</h3>
<p>It&rsquo;ll make your daily job more interesting. You will learn lots of great new technologies. You&rsquo;ll become a better developer by learning from your peers. You&rsquo;ll improve your craft and expertise. It&rsquo;ll increase your employability. You&rsquo;ll even likely become an expert in your field!</p>
<h3 id="share-with-the-world">Share with the world!</h3>
<p>Write, blog, tweet, present about the things you&rsquo;ve learned at meetups or conferences. You&rsquo;ll learn a lot from others along the way. Write and share code, and/or contribute to open source projects. You&rsquo;ll meet awesome peers and mentors. And will create all sorts of interesting job opportunities.</p>
<h3 id="be-curious-and-share-with-the-world">Be curious and share with the world!</h3>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tip: Making a Google Cloud Storage bucket or file public</title><link>https://glaforge.dev/posts/2019/02/13/tip-making-a-google-cloud-storage-bucket-or-file-public/</link><pubDate>Wed, 13 Feb 2019 17:48:00 +0100</pubDate><guid>https://glaforge.dev/posts/2019/02/13/tip-making-a-google-cloud-storage-bucket-or-file-public/</guid><description>&lt;p>&lt;a href="https://cloud.google.com/storage/">Google Cloud Storage&lt;/a> is the ideal product to store your object files (binary files, pictures, audio/video assets, and more).&lt;/p>
&lt;p>Until recently, there was an option in the Google cloud console with a checkbox to quickly make a file or bucket public. However, and I would add &amp;ldquo;unfortunately&amp;rdquo;, users tended to inadvertently clicking the checkbox, thus making potentail confidential assets public. So this risky, but easy, option, has been removed to avoid any unwanted data leak.&lt;/p></description><content:encoded>
<![CDATA[<p><a href="https://cloud.google.com/storage/">Google Cloud Storage</a> is the ideal product to store your object files (binary files, pictures, audio/video assets, and more).</p>
<p>Until recently, there was an option in the Google cloud console with a checkbox to quickly make a file or bucket public. However, and I would add &ldquo;unfortunately&rdquo;, users tended to inadvertently clicking the checkbox, thus making potentail confidential assets public. So this risky, but easy, option, has been removed to avoid any unwanted data leak.</p>
<p>However, of course, it&rsquo;s still possible to <a href="https://cloud.google.com/storage/docs/access-control/making-data-public">make buckets or files stored in Cloud Storage public</a>. But you can&rsquo;t do it without paying attention! As I never quite remember how to do that (in spite of the linked documentation easily found with a quick Google search), I decided to highlight with a few screenshots how to achieve that!</p>
<p>I assume you already have or created a GCP project, and you also have a bucket full of assets that you want to make public, because you need to share them on the Web, for your mobile application, etc.</p>
<p>To illustrate this tip, let&rsquo;s have a look at the GCP cloud console:</p>
<p><figure>
  <a href="#img-0f8a2395305df39980b10feb32241c6a">
    <img src="/img/public-bucket/gcs-01-file-browser-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0f8a2395305df39980b10feb32241c6a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/public-bucket/gcs-01-file-browser-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="making-a-file-public">Making a file public</h2>
<p>First, we&rsquo;ll have a look at making a single file public.</p>
<p>You&rsquo;ll have to click the vertical triple dot icon on the right of the screen, and click on <code>Edit permissions</code>:</p>
<p><figure>
  <a href="#img-2bb5cfa46956844a7ffa786793f249b4">
    <img src="/img/public-bucket/gcs-02-permissions-drop-down-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-2bb5cfa46956844a7ffa786793f249b4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/public-bucket/gcs-02-permissions-drop-down-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Once you&rsquo;ve clicked on this option, you&rsquo;ll be given the choice to add new permissions to members or groups of members:</p>
<p><figure>
  <a href="#img-ad5de9be1fc6b90c4955603308e6fb13">
    <img src="/img/public-bucket/gcs-03-permissions-dialog-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ad5de9be1fc6b90c4955603308e6fb13">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/public-bucket/gcs-03-permissions-dialog-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In our case, we want to allow all the users to have read access on that particular file. So I&rsquo;m giving the <code>Group</code> of <code>allUsers</code> the <code>Reader</code> access level. Then, once saved, in the file browser, you should see the following icon warning you the file is now public:</p>
<p><figure>
  <a href="#img-a4fda09774d00e24cafa1f913e7e88ff">
    <img src="/img/public-bucket/gcs-04-public-warning-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a4fda09774d00e24cafa1f913e7e88ff">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/public-bucket/gcs-04-public-warning-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="making-a-bucket-public">Making a bucket public</h2>
<p>Instead of doing this for each individual file, you can also do the same at the bucket level, to give read access to the bucket and all its files in one go.</p>
<p>From the object browser, click on the <code>Permissions</code> tab. You will have to add the <code>allUsers</code> members the <code>Storage Object Viewer</code> role:</p>
<p><figure>
  <a href="#img-94be41107fe2a1cd54451408f9f61ad2">
    <img src="/img/public-bucket/gcs-05-bucket-permissions-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-94be41107fe2a1cd54451408f9f61ad2">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/public-bucket/gcs-05-bucket-permissions-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Click on the <code>Add members button</code>, type <code>allUsers</code>, select the <code>Storage &gt; Storage Object Viewer</code> option, as follows, and click <code>add</code>:</p>
<p><figure>
  <a href="#img-8babe3065da6498b4474f1ad5fda25e4">
    <img src="/img/public-bucket/gcs-06-add-role-to-bucket-users-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8babe3065da6498b4474f1ad5fda25e4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/public-bucket/gcs-06-add-role-to-bucket-users-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Now if you head back to the file browser, you&rsquo;ll see all the files have the little warning icon telling you the resource is publicly accessible.</p>
<h2 id="for-command-line-gurus">For command-line gurus</h2>
<p>I showed the visual approach from the cloud console&hellip; but there&rsquo;s a one-liner you can use, thanks to the <a href="https://cloud.google.com/storage/docs/gsutil">gsutil</a> command.</p>
<p>For an individual file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gsutil acl ch -u AllUsers:R gs://<span style="color:#666">[</span>BUCKET_NAME<span style="color:#666">]</span>/<span style="color:#666">[</span>OBJECT_NAME<span style="color:#666">]</span>
</span></span></code></pre></div><p>For a whole bucket:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gsutil iam ch allUsers:objectViewer gs://<span style="color:#666">[</span>BUCKET_NAME<span style="color:#666">]</span>
</span></span></code></pre></div><p>(Where you replace <code>[BUCKET_NAME]</code> with your project name, and <code>[OBJECT_NAME]</code> with the file name)</p>
<h2 id="more">More&hellip;</h2>
<p>There&rsquo;s also a REST API that you can use to handle your buckets and file, as well as different client libraries in different languages that you can use as well.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Mais c'est quoi un Developer Advocate ?</title><link>https://glaforge.dev/posts/2019/02/04/mais-c-est-quoi-un-developer-advocate/</link><pubDate>Mon, 04 Feb 2019 17:59:07 +0100</pubDate><guid>https://glaforge.dev/posts/2019/02/04/mais-c-est-quoi-un-developer-advocate/</guid><description>&lt;p>J&amp;rsquo;ai eu le plaisir d&amp;rsquo;encadrer des stagiaires de 3ème récemment chez Google. Nous accueillons des enfants, neveux, nièces, cousins d&amp;rsquo;employés de Google (donc non :-P, je ne prends pas de stagiaire, pas la peine de demander !!!) pour leur faire découvrir les différents métiers que nous exerçons dans l&amp;rsquo;entreprise. Et il y en a beaucoup !&lt;/p>
&lt;p>L&amp;rsquo;un de mes stagiaires m&amp;rsquo;a interviewé lorsque je décrivais mon travail de &amp;ldquo;Developer Advocate&amp;rdquo;, au sein de Google Cloud. J&amp;rsquo;ai trouvé cette interview intéressante, et je me suis dit que ça valait le coup de la partager avec vous, en Français (si, si, j&amp;rsquo;écris en Français parfois sur ce blog.)&lt;/p></description><content:encoded>
<![CDATA[<p>J&rsquo;ai eu le plaisir d&rsquo;encadrer des stagiaires de 3ème récemment chez Google. Nous accueillons des enfants, neveux, nièces, cousins d&rsquo;employés de Google (donc non :-P, je ne prends pas de stagiaire, pas la peine de demander !!!) pour leur faire découvrir les différents métiers que nous exerçons dans l&rsquo;entreprise. Et il y en a beaucoup !</p>
<p>L&rsquo;un de mes stagiaires m&rsquo;a interviewé lorsque je décrivais mon travail de &ldquo;Developer Advocate&rdquo;, au sein de Google Cloud. J&rsquo;ai trouvé cette interview intéressante, et je me suis dit que ça valait le coup de la partager avec vous, en Français (si, si, j&rsquo;écris en Français parfois sur ce blog.)</p>
<p>L&rsquo;exercice de cet entretien est intéressant parce qu&rsquo;il me permet d&rsquo;essayer d&rsquo;expliquer de manière simple ce que je fais&hellip; et ce n&rsquo;est pas évidant, vu la technicité de cet univers, des produits sur lesquels on travaille (qui ne sont pas à la portée du grand public). Donc voici quelques unes des questions auxquelles j&rsquo;ai eu à répondre. J&rsquo;espère que vous trouverez cela intéressant.</p>
<h2 id="quel-est-ton-métier-">Quel est ton métier ?</h2>
<p>Je suis <strong>Developer Advocate</strong> pour la branche <strong>Google Cloud</strong> de Google.</p>
<p>C&rsquo;est un titre en anglais qui n&rsquo;a pas vraiment d&rsquo;équivalent en Français.</p>
<p><a href="https://cloud.google.com/">Google Cloud</a>, c&rsquo;est la partie de Google qui propose des produits comme par exemple Gmail pour la messagerie électronique, Google Docs pour le traitement de texte ou de tableurs, mais qui offre également aux développeurs et ingénieurs des serveurs sur lesquels les développeurs peuvent héberger leurs applications, des bases de données pour stocker leurs données, ou bien encore des services autour de l&rsquo;intelligence artificielle pour reconnaître ce qu&rsquo;il y a comme objets dans des images.</p>
<h2 id="quelles-sont-les-principales-activités-dans-ce-métier-">Quelles sont les principales activités dans ce métier ?</h2>
<p>Le rôle d&rsquo;un Developer Advocate est d&rsquo;être une sorte de représentant des développeurs (nos utilisateurs et clients qui créent des applications). Nous collectons les retours de ces développeurs pour voir comment améliorer les produits de Google, en écoutant leurs besoins, leurs demandes, les problèmes qu&rsquo;ils rencontrent.</p>
<p>Par ailleurs, une autre grande partie de ce rôle (la partie la plus visible de l&rsquo;extérieur), c&rsquo;est de présenter et promouvoir les produits de Google (Google Cloud dans mon cas). Il s&rsquo;agit alors d&rsquo;écrire des articles, de coder des exemples de programmes utilisant nos technologies, enregistrer des vidéos, préparer des présentations que nous donnons lors de conférences locales ou internationales, puis aussi tirer partie des réseaux sociaux pour faire passer notre message.</p>
<p>Enfin, nous sommes également les utilisateurs &ldquo;zéro&rdquo; de nos futurs nouveaux produits. Nous sommes donc les premiers à tester ces produits, pour voir ce qui fonctionne ou pas, s&rsquo;ils sont faciles à utiliser, s&rsquo;ils ont des bugs, si l&rsquo;on peut trouver des façons de les améliorer ou de les enrichir avant de les sortir publiquement.</p>
<h2 id="travailles-tu-plutôt-seul-ou-en-équipe--pourquoi-">Travailles-tu plutôt seul ou en équipe ? Pourquoi ?</h2>
<p>Je travaille dans une équipe internationale, répartie dans différents pays, avec des bureaux à San Francisco, Seattle, New York, Londres, Paris ou Tokyo. Parfois je travaille effectivement en équipe, à plusieurs sur un même sujet, mais beaucoup de mes activités sont aussi solitaires, comme lorsque l&rsquo;on prépare une démonstration et une présentation pour une conférence.</p>
<h2 id="dois-tu-prendre-des-initiatives--pourquoi-">Dois-tu prendre des initiatives ? Pourquoi ?</h2>
<p>Le poste de Developer Advocate est un travail qui demande beaucoup d&rsquo;autonomie, et donc qui nécessite de prendre soi même beaucoup d&rsquo;initiatives. C&rsquo;est à moi de décider sur quel sujet travailler, quel article écrire, à quelle conférence je souhaite assister.</p>
<p>Parfois, les Developer Advocates ont des demandes venant des chefs de produits qui souhaiteraient que nous représentions leur produit, mais c&rsquo;est généralement par soi même que l&rsquo;on décide sur quoi travailler.</p>
<h2 id="as-tu-des-responsabilités--si-oui-lesquelles--pourquoi-">As-tu des responsabilités ? Si oui lesquelles ? Pourquoi ?</h2>
<p>Je n&rsquo;encadre personne, donc je n&rsquo;ai en tout cas pas de responsabilité managériales. Par contre, je me focalise sur une certaine gamme de produits, et c&rsquo;est de ma responsabilité de représenter et d&rsquo;améliorer ces produits là.</p>
<h2 id="as-tu-des-contacts-avec-des-personnes-autres-que-tes-collègues-de-travail--pourquoi-">As-tu des contacts avec des personnes autres que tes collègues de travail ? Pourquoi ?</h2>
<p>Comme une partie de mon travail consiste à aller présenter les produits de Google Cloud à des conférences, des &ldquo;meetups&rdquo;, des groupes d&rsquo;utilisateurs, je rencontre effectivement beaucoup de monde lors de ces événements. Egalement au travers des réseaux sociaux, j&rsquo;interagis souvent avec des développeurs aux quatre coins du monde. Et j&rsquo;ai également la chance de pouvoir rencontrer nos clients, pour discuter de leurs problèmes, de leurs besoins, mais aussi de leurs réussites.</p>
<h2 id="les-activités-sont-elles-variées-ou-répétitives-">Les activités sont-elles variées ou répétitives ?</h2>
<p>Les activités sont effectivement variées, aussi bien de la programmation (pour préparer des démonstrations des produits), de la communication (pour des présentations), de l&rsquo;écriture (pour les articles, la documentation que nous rédigeons), mais aussi le voyage au travers le monde permets de découvrir de nouveaux horizons et de nouvelles cultures.</p>
<h2 id="dois-tu-organiser-ton-travail-ou-plutôt-exécuter-des-consignes-">Dois-tu organiser ton travail ou plutôt exécuter des consignes ?</h2>
<p>Comme je le disais plus haut, c&rsquo;est un travail qui demande beaucoup d&rsquo;autonomie, donc c&rsquo;est à moi d&rsquo;organiser mon travail, d&rsquo;autant plus que j&rsquo;ai déjà un certain niveau d&rsquo;ancienneté. Il arrive parfois qu&rsquo;on me demande certaines choses effectivement, mais le plus souvent c&rsquo;est à moi de m&rsquo;organiser.</p>
<h2 id="est-ce-que-le-travail-est-fatigant-nerveusement-et-physiquement--pourquoi-">Est-ce que le travail est fatigant (nerveusement et physiquement) ? Pourquoi ?</h2>
<p>Parfois oui. Il y a beaucoup à faire : Google Cloud possède de nombreux produits, qui sont développés rapidement, il y a donc énormément de choses à couvrir et étudier. Ce qui est également fatigant, c&rsquo;est le fait de voyager au travers le monde avec les décalages horaires, les tracas associés au voyage de manière générale.</p>
<p>Outre le voyage et la quantité de travail, il faut travailler régulièrement avec la maison mère qui est sur la côte Ouest des Etats-Unis, avec 9 heures de décalage horaire, ce qui veut dire qu&rsquo;il faut pouvoir faire des conférences téléphoniques avec mes collègues outre-Atlantique après 18 ou 19 heures, voire parfois même à 21 ou 22 heures ! Autre exemple, lorsque l&rsquo;on donne des présentations à des &ldquo;meetups&rdquo;, c&rsquo;est souvent le soir après le travail, donc on peut rentrer parfois très tard chez soi.</p>
<h2 id="est-ce-que-cette-profession-permet-dobtenir-une-promotion--laquelle-">Est-ce que cette profession permet d&rsquo;obtenir une promotion ? Laquelle ?</h2>
<p>Dans ce rôle, il y a différents grades, différents niveaux. Quand on commence dans ce métier, on entrera à un certain niveau, et au fil des années, on pourra progresser et monter de niveau en niveau. L&rsquo;entreprise n&rsquo;attends pas la même chose de ses employés en fonction de leur niveau d&rsquo;ancienneté. Nous avons une sorte d&rsquo;échelle qui décrit ce qui est attendu de chacun à chaque niveau de sa progression hiérarchique.</p>
<h2 id="quels-sont-les-diplômes-requis-">Quels sont les diplômes requis ?</h2>
<p>Il faut généralement un Bac+5 (type mastère, école d&rsquo;ingénieur) en informatique et avoir déjà une certaine expérience professionnelle dans le domaine de l&rsquo;informatique pour bien comprendre la problématique de nos utilisateurs et clients.</p>
<h2 id="quel-est-le-contenu-des-études-">Quel est le contenu des études ?</h2>
<p>Dans mon cas (un DESS en informatique), ce sont des études qui permettent aux étudiants d&rsquo;apprendre comment fonctionnent les ordinateurs, d&rsquo;apprendre des langages de programmation, comment écrire des algorithmes, comment fonctionne les réseaux informatiques ou les bases de données, etc.</p>
<h2 id="est-ce-quil-y-a-dautres-formations-possibles-formation-continue-cours-du-soir-autres-pour-cette-profession-">Est-ce qu&rsquo;il y a d&rsquo;autres formations possibles (formation continue, cours du soir, autres) pour cette profession ?</h2>
<p>Bien qu&rsquo;un diplôme Bac+5 en informatique soit le plus commun, du moment qu&rsquo;on a une bonne expérience professionnelle dans le domaine, il est aussi possible de bifurquer vers cette profession. Mais il n&rsquo;y a à proprement parlé pas de formation spécifique pour le rôle de Developer Advocate à ma connaissance.</p>
<p>On demande souvent aux jeunes &ldquo;qu&rsquo;est-ce que tu voudras faire plus tard ?&rdquo; </p>
<p>Dans mon cas, je voulais être &ldquo;ingénieur&rdquo;, et j&rsquo;aimais bien l&rsquo;informatique, mais ce qui est amusant c&rsquo;est qu&rsquo;à l&rsquo;époque où j&rsquo;ai fait mes études, le métier de &ldquo;Developer Advocate&rdquo; n&rsquo;existait pas vraiment. Donc qui peut dire quels métiers existeront quand vous serez sur le marché du travail ? Il faut savoir s&rsquo;adapter, car de nouveaux métiers voient le jour régulièrement, et il n&rsquo;y aura pas forcément de formation spécialisée pour y parvenir, ou de parcours type.</p>
<h2 id="quelle-sont-les-qualités-requises-">Quelle sont les qualités requises ?</h2>
<p>Outre le fait d&rsquo;être un bon ingénieur en informatique (savoir programmer, etc.), il faut aussi avoir de très bonnes capacités communicationnelles, un esprit de synthèse et de vulgarisation, car nous devons aider nos utilisateurs et clients à comprendre comment se servir de nos produits.</p>
<p>Par ailleurs, la majeure partie de mon travail s&rsquo;effectue en anglais, qui est la langue de la maison mère, mais la langue internationale en informatique et lorsque l&rsquo;on voyage. Donc il faut absolument être bon en Anglais pour ce travail.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Machine learning APIs with Apache Groovy</title><link>https://glaforge.dev/talks/2019/01/18/machine-learning-apis-with-apache-groovy/</link><pubDate>Fri, 18 Jan 2019 18:07:48 +0100</pubDate><guid>https://glaforge.dev/talks/2019/01/18/machine-learning-apis-with-apache-groovy/</guid><description>&lt;p>At &lt;a href="https://gr8conf.eu/">GR8Conf Europe&lt;/a> last year, I &lt;a href="https://2018.gr8conf.eu/talks/610">talked&lt;/a> 
about how to take advantage of the &lt;a href="https://cloud.google.com/products/ai/building-blocks/">Google Cloud machine learning APIs&lt;/a> 
using &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a>.&lt;/p>
&lt;p>With Groovy, you can call the Vision API that recognises what&amp;rsquo;s in your pictures, or reads text.&lt;/p>
&lt;p>You can invoke the Natural Language API to understand the structure of your text.&lt;/p>
&lt;p>With the Speech-To-Text API, you can get transcriptions of what&amp;rsquo;s been said in an audio stream, or with Text-To-Spech,
you can also generate human-like voices from your own text.&lt;/p></description><content:encoded>
<![CDATA[<p>At <a href="https://gr8conf.eu/">GR8Conf Europe</a> last year, I <a href="https://2018.gr8conf.eu/talks/610">talked</a> 
about how to take advantage of the <a href="https://cloud.google.com/products/ai/building-blocks/">Google Cloud machine learning APIs</a> 
using <a href="http://groovy-lang.org/">Apache Groovy</a>.</p>
<p>With Groovy, you can call the Vision API that recognises what&rsquo;s in your pictures, or reads text.</p>
<p>You can invoke the Natural Language API to understand the structure of your text.</p>
<p>With the Speech-To-Text API, you can get transcriptions of what&rsquo;s been said in an audio stream, or with Text-To-Spech,
you can also generate human-like voices from your own text.</p>
<p>With those ready-made APIs, no need to be an expert data scientist! Just call an SDK or a REST API, and you&rsquo;re ready to go.</p>
<p>Here&rsquo;s the video of the presentation:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/WKK1iUoB6CE?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And here are the slides that I showed:</p>
<script async class="speakerdeck-embed" data-id="c2927b99b7b64b9c83a90fe81704a9f2" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>In a <a href="http://glaforge.appspot.com/article/vision-recognition-with-a-groovy-twist">previous article</a>,
I also presented one of the sample that I used with the Vision API.</p>
<p>The <a href="https://www.gr8conf.eu/cfp">Call for Paper</a> for the new edition of GR8Conf Europe is still open,
so don&rsquo;t miss the opportunity to tell the world about all the cool things you&rsquo;ve been doing with Apache Groovy!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Interview InfoQ en Français sur les microservices sur Google Cloud Platform</title><link>https://glaforge.dev/posts/2019/01/11/interview-infoq-en-francais-sur-les-microservices-sur-google-cloud-platform/</link><pubDate>Fri, 11 Jan 2019 22:11:49 +0100</pubDate><guid>https://glaforge.dev/posts/2019/01/11/interview-infoq-en-francais-sur-les-microservices-sur-google-cloud-platform/</guid><description>&lt;p>Une fois n&amp;rsquo;est pas coutume, je vais parler de Google Cloud Platform en français !
Lors de la conférence Voxxed Days Microservices, que j&amp;rsquo;ai 
&lt;a href="https://glaforge.dev/talks/2019/01/03/new-serverless-solutions-on-google-cloud-for-functions-apps-and-containers/">couverte récemment&lt;/a>,
j&amp;rsquo;ai eu l&amp;rsquo;occasion de répondre à une interview pour InfoQ France.&lt;/p>
&lt;p>&lt;a href="https://www.infoq.com/fr/interviews/voxxeddays-microservices-2018-guillaume-laforge">Interview sur les microservices sur Google Cloud Platform&lt;/a>&lt;/p>
&lt;p>Voici la liste des questions auxquelles j&amp;rsquo;ai répondues, et je vous laisserai écouter les réponses sur 
&lt;a href="https://www.infoq.com/fr/interviews/voxxeddays-microservices-2018-guillaume-laforge">InfoQ France&lt;/a> !&lt;/p>
&lt;ul>
&lt;li>Pour ceux qui ne te connaissent pas, peux-tu nous dire qui tu es ?&lt;/li>
&lt;li>Elles sont où les équipes produits ?&lt;/li>
&lt;li>Et les utilisateurs, en France, il y en a beaucoup ?&lt;/li>
&lt;li>Pour les néophytes, les microservices, qu&amp;rsquo;est-ce que c&amp;rsquo;est ?&lt;/li>
&lt;li>C&amp;rsquo;est quoi le &amp;ldquo;nouveau&amp;rdquo; par rapport aux architectures dites distribuées, soa, webservices ?&lt;/li>
&lt;li>On va parler de la platforme cloud de google, où ça en est ?&lt;/li>
&lt;li>C&amp;rsquo;est quoi serverless, le retour du mainframe ?&lt;/li>
&lt;li>Quelles nouveautés ?&lt;/li>
&lt;li>Et Google vis à vis de java ?&lt;/li>
&lt;li>C&amp;rsquo;est quoi les langages que vous poussez le plus ?&lt;/li>
&lt;li>Le futur des microservices ?&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Une fois n&rsquo;est pas coutume, je vais parler de Google Cloud Platform en français !
Lors de la conférence Voxxed Days Microservices, que j&rsquo;ai 
<a href="https://glaforge.dev/talks/2019/01/03/new-serverless-solutions-on-google-cloud-for-functions-apps-and-containers/">couverte récemment</a>,
j&rsquo;ai eu l&rsquo;occasion de répondre à une interview pour InfoQ France.</p>
<p><a href="https://www.infoq.com/fr/interviews/voxxeddays-microservices-2018-guillaume-laforge">Interview sur les microservices sur Google Cloud Platform</a></p>
<p>Voici la liste des questions auxquelles j&rsquo;ai répondues, et je vous laisserai écouter les réponses sur 
<a href="https://www.infoq.com/fr/interviews/voxxeddays-microservices-2018-guillaume-laforge">InfoQ France</a> !</p>
<ul>
<li>Pour ceux qui ne te connaissent pas, peux-tu nous dire qui tu es ?</li>
<li>Elles sont où les équipes produits ?</li>
<li>Et les utilisateurs, en France, il y en a beaucoup ?</li>
<li>Pour les néophytes, les microservices, qu&rsquo;est-ce que c&rsquo;est ?</li>
<li>C&rsquo;est quoi le &ldquo;nouveau&rdquo; par rapport aux architectures dites distribuées, soa, webservices ?</li>
<li>On va parler de la platforme cloud de google, où ça en est ?</li>
<li>C&rsquo;est quoi serverless, le retour du mainframe ?</li>
<li>Quelles nouveautés ?</li>
<li>Et Google vis à vis de java ?</li>
<li>C&rsquo;est quoi les langages que vous poussez le plus ?</li>
<li>Le futur des microservices ?</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>New Serverless Solutions on Google Cloud for Functions Apps and Containers</title><link>https://glaforge.dev/talks/2019/01/03/new-serverless-solutions-on-google-cloud-for-functions-apps-and-containers/</link><pubDate>Thu, 03 Jan 2019 22:14:19 +0100</pubDate><guid>https://glaforge.dev/talks/2019/01/03/new-serverless-solutions-on-google-cloud-for-functions-apps-and-containers/</guid><description>&lt;p>At &lt;a href="https://voxxeddays.com/microservices/">Voxxed Days Microservices&lt;/a>, in Paris,
I talked about the latest development in serverless solutions on &lt;a href="https://cloud.google.com/products/">Google Cloud Platform&lt;/a>,
to deploy &lt;a href="https://cloud.google.com/functions/">functions&lt;/a>, &lt;a href="https://cloud.google.com/appengine/">apps&lt;/a> 
and even &lt;a href="https://cloud.google.com/blog/products/gcp/bringing-the-best-of-serverless-to-you">containers&lt;/a>.&lt;/p>
&lt;p>I answered an &lt;a href="https://voxxeddays.com/microservices/2018/09/16/guillaume-laforge-on-serverless-solutions-on-google-cloud/">interview&lt;/a> 
on the theme of microservices, and how this maps to the Google cloud products.&lt;/p>
&lt;p>And the video of my presentation was published on YouTube:&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Ke89_rFx7tI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>Here&amp;rsquo;s the abstract of the session:&lt;/p></description><content:encoded>
<![CDATA[<p>At <a href="https://voxxeddays.com/microservices/">Voxxed Days Microservices</a>, in Paris,
I talked about the latest development in serverless solutions on <a href="https://cloud.google.com/products/">Google Cloud Platform</a>,
to deploy <a href="https://cloud.google.com/functions/">functions</a>, <a href="https://cloud.google.com/appengine/">apps</a> 
and even <a href="https://cloud.google.com/blog/products/gcp/bringing-the-best-of-serverless-to-you">containers</a>.</p>
<p>I answered an <a href="https://voxxeddays.com/microservices/2018/09/16/guillaume-laforge-on-serverless-solutions-on-google-cloud/">interview</a> 
on the theme of microservices, and how this maps to the Google cloud products.</p>
<p>And the video of my presentation was published on YouTube:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Ke89_rFx7tI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Here&rsquo;s the abstract of the session:</p>
<blockquote>
<p>Plenty of novelties in the Serverless offering of Google Cloud Platform, whether you&rsquo;re developing functions, apps or containers.</p>
<p>Let&rsquo;s get started with the new modern runtimes for the venerable Google App Engine,
sandboxed thanks to the open source gVisor container sandboxing technology.
Cloud Functions is now GA with Node.js, but also offers new languages like Python to let you implement your functions.
If you need more flexibility, you will also be able to run serverless containers: just dockerize your project and off you go!</p>
<p>But the crux of the show has to be the new open source project, Knative, a collaboration of Google with key vendors like Pivotal,
IBM, Red Hat or SAP, which offers a set of portable building blocks on top of Kubernetes to build serverless platforms.
Additionally, you will be able to try out Knative on Google Kubernetes Engine thanks to a dedicated add-on.</p>
<p>In this session, we&rsquo;ll review all the new serverless-related features of Google Cloud Platform with concrete demos,
so you can get started easily and rapidly.</p></blockquote>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Deploy a Micronaut application containerized with JIB to Google Kubernetes Engine</title><link>https://glaforge.dev/posts/2018/11/26/deploy-a-micronaut-application-containerized-with-jib-to-google-kubernetes-engine/</link><pubDate>Mon, 26 Nov 2018 22:31:16 +0100</pubDate><guid>https://glaforge.dev/posts/2018/11/26/deploy-a-micronaut-application-containerized-with-jib-to-google-kubernetes-engine/</guid><description>&lt;p>A few weeks ago, I had the chance to be at &lt;a href="https://devoxx.be/">Devoxx Belgium&lt;/a> once again, to meet developers and learn about new things from awesome speakers. Google Cloud Platform had its own booth on the exhibition floor, and the team was running codelabs: 10 laptops were at the disposal of attendees to go through various hands-on tutorials on several GCP products. I took a chance at crafting my own codelab: deploying a Micronaut application, containerized with Jib, to Google Kubernetes Engine.&lt;/p></description><content:encoded>
<![CDATA[<p>A few weeks ago, I had the chance to be at <a href="https://devoxx.be/">Devoxx Belgium</a> once again, to meet developers and learn about new things from awesome speakers. Google Cloud Platform had its own booth on the exhibition floor, and the team was running codelabs: 10 laptops were at the disposal of attendees to go through various hands-on tutorials on several GCP products. I took a chance at crafting my own codelab: deploying a Micronaut application, containerized with Jib, to Google Kubernetes Engine.</p>
<p>For the impatient, follow this link: <a href="https://g.co/codelabs/micronaut">g.co/codelabs/micronaut</a></p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>If you haven&rsquo;t got a GCP account already, know that there&rsquo;s a <a href="https://console.cloud.google.com/freetrial">free trial with $300 of cloud credits</a> to get started.</p>
      </div>
    </div><p>More information on the tools used:</p>
<ul>
<li>
<p><a href="http://micronaut.io/">Micronaut</a> is a modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications. Micronaut aims to deliver great startup time, fast throughput, with a minimal memory footprint. Developers can develop with Micronaut in Java, Groovy or Kotlin.</p>
</li>
<li>
<p>Jib is an open source tool that lets you build Docker and OCI images for your Java applications. It is available as plugins for Maven and Gradle, and as a Java library.</p>
</li>
<li>
<p><a href="https://kubernetes.io/">Kubernetes</a> is an open source project which can run in many different environments, from laptops to high-availability multi-node clusters, from public clouds to on-premise deployments, from virtual machines to bare metal.</p>
</li>
<li>
<p><a href="https://cloud.google.com/kubernetes-engine/">Google Kubernetes Engine</a> is Google Cloud Platform&rsquo;s hosted Kubernetes platform.</p>
</li>
</ul>
<p>In this codelab, you deploy a simple Groovy-based Micronaut microservice to Kubernetes running on Kubernetes Engine.</p>
<p>The goal of this codelab is for you to run your microservice as a replicated service running on Kubernetes. You take code that you have developed on your machine, turn it into a Docker container image built with Jib, and then run and scale that image on Kubernetes Engine.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>An Intro to Google Cloud Platform</title><link>https://glaforge.dev/talks/2018/10/25/an-intro-to-google-cloud-platform/</link><pubDate>Thu, 25 Oct 2018 22:34:39 +0100</pubDate><guid>https://glaforge.dev/talks/2018/10/25/an-intro-to-google-cloud-platform/</guid><description>&lt;p>In a matter of a few years, Google Cloud Platform has evolved from a very small set of products or APIs to a wealth of close to a hundred of products, services and APIs that developers can take advantage of.&lt;/p>
&lt;p>This week, at the event &lt;a href="https://www.meilleurdevdefrance.com/">Le Meilleur Dev de France&lt;/a>, I gave an introduction to the whole platform, focusing on three key axis: compute, storage and machine learning. After an introduction on famous users of GCP, like Snapchat, Spotify or PokemonGo, I also gave a few examples of big French companies as well as French startups who have decided to go to the cloud with Google.&lt;/p></description><content:encoded>
<![CDATA[<p>In a matter of a few years, Google Cloud Platform has evolved from a very small set of products or APIs to a wealth of close to a hundred of products, services and APIs that developers can take advantage of.</p>
<p>This week, at the event <a href="https://www.meilleurdevdefrance.com/">Le Meilleur Dev de France</a>, I gave an introduction to the whole platform, focusing on three key axis: compute, storage and machine learning. After an introduction on famous users of GCP, like Snapchat, Spotify or PokemonGo, I also gave a few examples of big French companies as well as French startups who have decided to go to the cloud with Google.</p>
<p>Later on, over the course of three sections, as I was covering the multiple solutions in each areas (compute / storage / ML), I also tried to give concrete hints as to when to use what, depending on your application needs. Indeed, as many solutions are at your disposal, comes the paradox of choice, as with more options, choice becomes more complicated.</p>
<p>Here are the slides I presented:</p>
<script async class="speakerdeck-embed" data-id="5bc87feeb5914782a2476411c01d353a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Nice Series on Spring Cloud Integration for Google Cloud Platform</title><link>https://glaforge.dev/posts/2018/09/17/nice-series-on-spring-cloud-integration-for-google-cloud-platform/</link><pubDate>Mon, 17 Sep 2018 22:36:43 +0100</pubDate><guid>https://glaforge.dev/posts/2018/09/17/nice-series-on-spring-cloud-integration-for-google-cloud-platform/</guid><description>&lt;p>My friend and former colleague &lt;a href="https://twitter.com/starbuxman">Josh Long&lt;/a> wrote a nice series of articles showing the Spring Cloud integration for Google Cloud Platform. The series contains 8 articles, covering:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://spring.io/blog/2018/08/20/bootiful-gcp-getting-started-with-spring-cloud-for-google-cloud-platform-1-8">the setup &amp;amp; authentication&lt;/a>, &lt;/li>
&lt;li>how to &lt;a href="https://spring.io/blog/2018/08/23/bootiful-gcp-relational-data-access-with-spring-cloud-gcp-2-8">access relational databases&lt;/a>, &lt;/li>
&lt;li>how to &lt;a href="https://spring.io/blog/2018/08/27/bootiful-gcp-globally-consistent-data-access-with-spanner-3-8">connect to Spanner&lt;/a>, &lt;/li>
&lt;li>how to &lt;a href="https://spring.io/blog/2018/08/30/bootiful-gcp-integration-with-google-cloud-pub-sub-4-8">send/receive messages with Pub/Sub&lt;/a>, &lt;/li>
&lt;li>how to &lt;a href="https://spring.io/blog/2018/09/03/bootiful-gcp-runtime-configuration-with-spring-cloud-gcp-runtime-config-5-8">use the runtime configuration&lt;/a>, &lt;/li>
&lt;li>how to look into &lt;a href="https://spring.io/blog/2018/09/06/bootiful-gcp-supporting-observability-with-spring-cloud-gcp-stackdriver-trace-6-8">Stackdriver tracing&lt;/a>, &lt;/li>
&lt;li>how to &lt;a href="https://spring.io/blog/2018/09/10/bootiful-gcp-use-spring-cloud-gcp-to-connect-to-other-gcp-services-7-8">access other services like the machine learning APIs&lt;/a>, &lt;/li>
&lt;li>and last but not least, how to &lt;a href="https://spring.io/blog/2018/09/13/bootiful-gcp-to-production-8-8">go to production&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>So if you&amp;rsquo;re using Spring and Spring Boot, this is the way to go for getting started on using Google Cloud Platform. For further reference, you can go read the &lt;a href="https://cloud.spring.io/spring-cloud-gcp/">documentation&lt;/a> that covers this integration.&lt;/p></description><content:encoded>
<![CDATA[<p>My friend and former colleague <a href="https://twitter.com/starbuxman">Josh Long</a> wrote a nice series of articles showing the Spring Cloud integration for Google Cloud Platform. The series contains 8 articles, covering:</p>
<ul>
<li><a href="https://spring.io/blog/2018/08/20/bootiful-gcp-getting-started-with-spring-cloud-for-google-cloud-platform-1-8">the setup &amp; authentication</a>, </li>
<li>how to <a href="https://spring.io/blog/2018/08/23/bootiful-gcp-relational-data-access-with-spring-cloud-gcp-2-8">access relational databases</a>, </li>
<li>how to <a href="https://spring.io/blog/2018/08/27/bootiful-gcp-globally-consistent-data-access-with-spanner-3-8">connect to Spanner</a>, </li>
<li>how to <a href="https://spring.io/blog/2018/08/30/bootiful-gcp-integration-with-google-cloud-pub-sub-4-8">send/receive messages with Pub/Sub</a>, </li>
<li>how to <a href="https://spring.io/blog/2018/09/03/bootiful-gcp-runtime-configuration-with-spring-cloud-gcp-runtime-config-5-8">use the runtime configuration</a>, </li>
<li>how to look into <a href="https://spring.io/blog/2018/09/06/bootiful-gcp-supporting-observability-with-spring-cloud-gcp-stackdriver-trace-6-8">Stackdriver tracing</a>, </li>
<li>how to <a href="https://spring.io/blog/2018/09/10/bootiful-gcp-use-spring-cloud-gcp-to-connect-to-other-gcp-services-7-8">access other services like the machine learning APIs</a>, </li>
<li>and last but not least, how to <a href="https://spring.io/blog/2018/09/13/bootiful-gcp-to-production-8-8">go to production</a>.</li>
</ul>
<p>So if you&rsquo;re using Spring and Spring Boot, this is the way to go for getting started on using Google Cloud Platform. For further reference, you can go read the <a href="https://cloud.spring.io/spring-cloud-gcp/">documentation</a> that covers this integration.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Building and deploying microservices with App Engine and Cloud Functions</title><link>https://glaforge.dev/talks/2018/08/06/building-and-deploying-microservices-with-app-engine-and-cloud-functions/</link><pubDate>Mon, 06 Aug 2018 22:46:46 +0100</pubDate><guid>https://glaforge.dev/talks/2018/08/06/building-and-deploying-microservices-with-app-engine-and-cloud-functions/</guid><description>&lt;p>A coupe weeks ago, I had the chance to talk at &lt;a href="https://cloud.withgoogle.com/next18/sf/">Cloud Next&lt;/a> 2018, in San Francisco, with my colleague and friend &lt;a href="https://twitter.com/alexismp">Alexis&lt;/a>. We talked about building and deploying microservices with Google App Engine and Cloud Functions. I&amp;rsquo;ve been a big fan of App Engine since 2009 when Google released the Java flavor, and have been enjoying doing a bit of Node / JavaScript on Cloud Functions since it came in beta. So I was very happy to be able to talk about those two serverless solutions.&lt;/p></description><content:encoded>
<![CDATA[<p>A coupe weeks ago, I had the chance to talk at <a href="https://cloud.withgoogle.com/next18/sf/">Cloud Next</a> 2018, in San Francisco, with my colleague and friend <a href="https://twitter.com/alexismp">Alexis</a>. We talked about building and deploying microservices with Google App Engine and Cloud Functions. I&rsquo;ve been a big fan of App Engine since 2009 when Google released the Java flavor, and have been enjoying doing a bit of Node / JavaScript on Cloud Functions since it came in beta. So I was very happy to be able to talk about those two serverless solutions.</p>
<p>Without further ado, let&rsquo;s start by sharing the video (and slides) of the talk!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/oALEthV9z_U?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Now that I&rsquo;ve shared the video, let me tell you a bit more about this session and the demo we built.</p>
<p><figure>
  <a href="#img-3e0fd5391c74eeb8020949be9c5628d4">
    <img src="/img/microservices-with-ae-cf/gae-gcf-01.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3e0fd5391c74eeb8020949be9c5628d4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/microservices-with-ae-cf/gae-gcf-01.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>First, a few words about the buzzword du jour: serverless. What I particularly like about this approach (which I also liked in the good old times of another old buzzword: PaaS) is how it lets me focus on the idea I want to implement, instead of being bothered from the get go with server or cluster provisioning, OS choice, monitoring/logging/alerting, etc. I can directly start coding something, and quickly deploy my app or function in the cloud and see how well it&rsquo;s working (compared to my dreamed up idea). Additionally, besides the ops-less aspect, I also don&rsquo;t have to think about scaling, as it scales pretty much auto-magically for me. Last but not least, I don&rsquo;t have to pay upfront big costs for renting machines or vms, as it&rsquo;s really pay as you go, and not paying for an idle server (after all, my idea might be just a quick idea not geared towards prime-time success!)</p>
<p><figure>
  <a href="#img-ff8bb916efeb567dd6c5ef951260605a">
    <img src="/img/microservices-with-ae-cf/gae-gcf-02.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ff8bb916efeb567dd6c5ef951260605a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/microservices-with-ae-cf/gae-gcf-02.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Google Cloud Platform offers various solutions which follow those characteristics, not only for compute with App Engine and Cloud Functions, but also for data storage (like Datastore which I&rsquo;m using in my demo as my database), or the machine learning APIs (like the Vision API that I also integrated in my app). Database-as-a-Service, Platform-as-a-Service, Function-as-a-Service, Software-as-a-Service often fall into that category for me, if you don&rsquo;t pay for infrastructure, when it takes care of scaling, and if it&rsquo;s price proportionally to your usage.</p>
<h2 id="cloud-functions">Cloud Functions</h2>
<p>Cloud Functions (or GCF for short) is a great fit for event driven problems: a new picture is stored on Google Cloud Storage? A function is triggered. I get a message on Pub/Sub? Another function is invoked. It&rsquo;s also possible to invoke a function directly via an HTTP call, without requiring any kind of gateway to expose it.</p>
<p>At Next, the general availability of Cloud Functions was announced, with an SLA of 99.5%, additional regions (2 in the US, 1 in Europe, 1 in APAC), and also new runtimes with Node.js 8 and Python 3.7. Further improvements are the ability to get a function hooked to a VPN in order to connect your functions with your VMs, new scaling controls to limit the number of instances serving your function, a direction connection to Cloud SQL to take advantage of GCP&rsquo;s great network instead of going through the wider public Internet, and the availability of environment variables to customize your deployments for example to tackle different environments like dev vs staging vs prod.</p>
<h2 id="app-engine">App Engine</h2>
<p>As I said, I&rsquo;ve always been a big fan of App Engine, long before I actually joined Google. This blog you&rsquo;re reading has been running on App Engine Java for many years! GAE (for short) is really a great fit for hosting web frontends or backend APIs, which are generally more long-lived that functions.</p>
<p>With Java 8 in GA last year, Node.js 8 in beta, new runtimes are also coming up: Python 3.7 and PHP 7.2. With the recently released new <a href="https://cloudplatform.googleblog.com/2018/05/Increase-performance-while-reducing-costs-with-the-new-App-Engine-scheduler.html">instance scheduler</a>, you have more control on your scaling which allows you to scale faster and have lower costs too. Deployments of new versions should also be faster with better caching and diff&rsquo;ing between versions.</p>
<p>During the hallway session, I had a nice conversation with an attendee who was pretty happy with the fact he&rsquo;d be able to have Python 3.7 for both Cloud Functions and App Engine, which will allow them to have a chance to share some code between projects.</p>
<p>The new runtimes are running on the <a href="https://cloudplatform.googleblog.com/2018/05/Open-sourcing-gVisor-a-sandboxed-container-runtime.html">gVisor sandbox container runtime</a> technology, a lightweight solution to isolate containers securely to run your payloads. A big advantage of gVisor is that App Engine runtimes are not limited anymore with things like the class whitelist which prevented usage of some particular classes.</p>
<p><figure>
  <a href="#img-1d8e05c712cec91f8df68ab78646b039">
    <img src="/img/microservices-with-ae-cf/gae-gcf-03.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1d8e05c712cec91f8df68ab78646b039">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/microservices-with-ae-cf/gae-gcf-03.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Back to the topic of microservices, App Engine has this concept of services. In your GCP project, your GAE application can run several services at the same time, potentially with different runtimes (for example a Java service and a Go service), and those services can be deployed with different versions.</p>
<p><figure>
  <a href="#img-2b259cedab91581e556f637e3abbd439">
    <img src="/img/microservices-with-ae-cf/gae-gcf-04.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-2b259cedab91581e556f637e3abbd439">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/microservices-with-ae-cf/gae-gcf-04.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Last thing I&rsquo;ll mention here for App Engine, that&rsquo;s the traffic splitting capability. You can easily split traffic (on the command-line or in the web UI) between different versions of a particular service. So for example if you want to do some A/B testing to see if users prefer the new feature or layout of your app, you can say that only 5% of incoming requests will be showing it, whereas the 95% of your users will continue to see the old version. This is also useful for canary deployments or blue / green deployments.</p>
<p><figure>
  <a href="#img-9752c0711ddfda2a1fc28481ea338b62">
    <img src="/img/microservices-with-ae-cf/gae-gcf-05.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9752c0711ddfda2a1fc28481ea338b62">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/microservices-with-ae-cf/gae-gcf-05.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>For my demo, I developed a simple picture sharing app. My web frontend is a Vue.js + App Engine Java backend using the SparkJava light framework. When a user takes a picture, it&rsquo;s uploaded to Google Cloud Storage, which triggers a Cloud Function which will store picture metadata in Datastore, and calls the Vision API to get the labels of things found in the picture, as well as check if the picture can be safely published (no racy, adult, spoof, violent content in it), and gives the dominant color in the image. Another function is triggered on at regular intervals to compute the most frequent tags (stored in Datastore), so a snapshot of them can be displayed in the dedicate page of the app.</p>
<p><figure>
  <a href="#img-b6b034a4572cb53191b234bfcee5ee9b">
    <img src="/img/microservices-with-ae-cf/gae-gcf-06.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b6b034a4572cb53191b234bfcee5ee9b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/microservices-with-ae-cf/gae-gcf-06.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="scaling-down">Scaling down&hellip;</h2>
<p>Getting to scale down towards our talk conclusion, we also shared a few words about the upcoming serverless containers for Cloud Functions, which we unveiled at the conference and on the GCP <a href="https://cloudplatform.googleblog.com/2018/07/bringing-the-best-of-serverless-to-you.html">blog post</a>. For serverless compute, you can deploy functions and apps, but we&rsquo;re seeing units of compute in the form of containers as well, and sometimes your project might need specific native libraries or particular compute abilities (like GPUs), or you simply want more control over your business logic&rsquo;s environments. So it makes sense to let you serve containers as well, in addition to functions and apps. If you&rsquo;re interested in trying out serverless containers, feel free to request access to the EAP program via <a href="http://g.co/serverlesscontainers">g.co/serverlesscontainers</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>In the top 20 Java influencers for 2018</title><link>https://glaforge.dev/posts/2018/07/30/in-the-top-20-java-influencers-for-2018/</link><pubDate>Mon, 30 Jul 2018 22:52:14 +0100</pubDate><guid>https://glaforge.dev/posts/2018/07/30/in-the-top-20-java-influencers-for-2018/</guid><description>&lt;p>&lt;figure>
&lt;a href="#img-a1ed18e1540a62d1b07c07e6017c02d8">
&lt;img src="https://glaforge.dev/img/misc/JAX_London_2018_Infografik_Influencer_Teaser_600x3700_47308_v1.jpg"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-a1ed18e1540a62d1b07c07e6017c02d8">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/JAX_London_2018_Infografik_Influencer_Teaser_600x3700_47308_v1.jpg"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>Just before heading to &lt;a href="https://cloud.withgoogle.com/next18/sf/">Google Cloud Next&lt;/a>, I was notified I was listed 4th in JAX London&amp;rsquo;s Top 20 Java influencers of 2018 on social media! It&amp;rsquo;s an honor to be listed among famous figures like Josh Bloch, Brian Goetze, Martin Thompson, Arun Gupta, Jessica Kerr, Mario Fusco, Josh Long, Venkat Subramanian, Charles Nutter and many others. You can see the full list of the top influencers &lt;a href="https://jaxlondon.com/blog/java-core-languages/top-20-java-influencers-of-2018/">here&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p><figure>
  <a href="#img-a1ed18e1540a62d1b07c07e6017c02d8">
    <img src="/img/misc/JAX_London_2018_Infografik_Influencer_Teaser_600x3700_47308_v1.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a1ed18e1540a62d1b07c07e6017c02d8">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/JAX_London_2018_Infografik_Influencer_Teaser_600x3700_47308_v1.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Just before heading to <a href="https://cloud.withgoogle.com/next18/sf/">Google Cloud Next</a>, I was notified I was listed 4th in JAX London&rsquo;s Top 20 Java influencers of 2018 on social media! It&rsquo;s an honor to be listed among famous figures like Josh Bloch, Brian Goetze, Martin Thompson, Arun Gupta, Jessica Kerr, Mario Fusco, Josh Long, Venkat Subramanian, Charles Nutter and many others. You can see the full list of the top influencers <a href="https://jaxlondon.com/blog/java-core-languages/top-20-java-influencers-of-2018/">here</a>.</p>
<p>I&rsquo;ll definitely continue to advocate for Java (and Apache Groovy) developers around the world, and share whatever I learn along the way through articles or conference talks. I&rsquo;m looking forward to meeting you, my fellow Java/Groovy developer friends, at an event near you.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Congratulations <a href="https://twitter.com/glaforge?ref_src=twsrc%5Etfw">@glaforge</a> for making it to the Top Social Influencers in Java 2018! <a href="https://t.co/rifptQeDcD">https://t.co/rifptQeDcD</a> <a href="https://t.co/kPYGXAaWeQ">pic.twitter.com/kPYGXAaWeQ</a></p>&mdash; JAX London (@jaxlondon) <a href="https://twitter.com/jaxlondon/status/1021684076779065344?ref_src=twsrc%5Etfw">July 24, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The Big Green Button Automating Continuous Delivery With Chatbots</title><link>https://glaforge.dev/talks/2018/06/25/the-big-green-button-automating-continuous-delivery-with-chatbots/</link><pubDate>Mon, 25 Jun 2018 09:19:50 +0100</pubDate><guid>https://glaforge.dev/talks/2018/06/25/the-big-green-button-automating-continuous-delivery-with-chatbots/</guid><description>&lt;p>Last month in sunny Napa valley, my awesome colleague &lt;a href="https://www.sethvargo.com/">Seth Vargo&lt;/a> and I had the chance to speak at &lt;a href="https://swampup.jfrog.com/">SwampUp&lt;/a>, the devops focused conference organized by &lt;a href="https://jfrog.com/">JFrog&lt;/a>. Our talk &amp;amp; demo were focused on the topic of &amp;ldquo;ChatOps&amp;rdquo;. But what is ChatOps? Here&amp;rsquo;s what our abstract said:&lt;/p>
&lt;blockquote>
&lt;p>Heard of ChatOps? It&amp;rsquo;s a movement in the DevOps community to take advantage of Chatbots. &lt;/p>
&lt;p>Chatbots centralize the conversation and history of your daily operations including build status, issue management, deployment, and monitoring, so that you access all the information and actions needed at the whim of a chat message in your team communication solution.&lt;/p></description><content:encoded>
<![CDATA[<p>Last month in sunny Napa valley, my awesome colleague <a href="https://www.sethvargo.com/">Seth Vargo</a> and I had the chance to speak at <a href="https://swampup.jfrog.com/">SwampUp</a>, the devops focused conference organized by <a href="https://jfrog.com/">JFrog</a>. Our talk &amp; demo were focused on the topic of &ldquo;ChatOps&rdquo;. But what is ChatOps? Here&rsquo;s what our abstract said:</p>
<blockquote>
<p>Heard of ChatOps? It&rsquo;s a movement in the DevOps community to take advantage of Chatbots. </p>
<p>Chatbots centralize the conversation and history of your daily operations including build status, issue management, deployment, and monitoring, so that you access all the information and actions needed at the whim of a chat message in your team communication solution.</p>
<p>After a quick introduction about the principles and pros&amp;cons of ChatOps, Guillaume Laforge &amp; Seth Vargo will walk you through Dialogflow to design your conversational interface to your CI/CD infrastructure. With the help of demos on the Google Cloud Platform, we&rsquo;ll see together how chatbots can make you more productive in your DevOps activities.</p></blockquote>
<p>The video has been published last week:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/gIE1gQGTc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Chatbots: switching the second gear</title><link>https://glaforge.dev/talks/2018/06/21/chatbots-switching-the-second-gear/</link><pubDate>Thu, 21 Jun 2018 13:39:51 +0100</pubDate><guid>https://glaforge.dev/talks/2018/06/21/chatbots-switching-the-second-gear/</guid><description>&lt;p>My buddy &lt;a href="https://twitter.com/manekinekko">Wassim&lt;/a> and I were back on stage together to talk about chatbots,
with &lt;a href="https://developers.google.com/actions/">Actions on Google&lt;/a> and &lt;a href="https://twitter.com/manekinekko">Dialogflow&lt;/a>,
at &lt;a href="https://devfest.gdglille.org/">DevFest Lille&lt;/a> and &lt;a href="http://bestofweb.paris/">Best of Web Paris&lt;/a>.
I&amp;rsquo;d like to share with you the slides of the presentation (the video has been recorded and will be available at a later time.)&lt;/p>
&lt;p>You might be interested in those two codelabs to get started on this journey:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://codelabs.developers.google.com/codelabs/actions-1/index.html?index=..%2F..%2Findex#0">Build Actions for the Google Assistant - level 1&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://codelabs.developers.google.com/codelabs/actions-2/index.html?index=..%2F..%2Findex#0">Build Actions for the Google Assistant - level 2&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s the presentation given at DevFest Lille:&lt;/p></description><content:encoded>
<![CDATA[<p>My buddy <a href="https://twitter.com/manekinekko">Wassim</a> and I were back on stage together to talk about chatbots,
with <a href="https://developers.google.com/actions/">Actions on Google</a> and <a href="https://twitter.com/manekinekko">Dialogflow</a>,
at <a href="https://devfest.gdglille.org/">DevFest Lille</a> and <a href="http://bestofweb.paris/">Best of Web Paris</a>.
I&rsquo;d like to share with you the slides of the presentation (the video has been recorded and will be available at a later time.)</p>
<p>You might be interested in those two codelabs to get started on this journey:</p>
<ul>
<li><a href="https://codelabs.developers.google.com/codelabs/actions-1/index.html?index=..%2F..%2Findex#0">Build Actions for the Google Assistant - level 1</a></li>
<li><a href="https://codelabs.developers.google.com/codelabs/actions-2/index.html?index=..%2F..%2Findex#0">Build Actions for the Google Assistant - level 2</a></li>
</ul>
<p>Here&rsquo;s the presentation given at DevFest Lille:</p>
<script async class="speakerdeck-embed" data-id="2466bf9bc1544289ba163b1fc4f2589a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>And at Best of Web Paris:</p>
<script async class="speakerdeck-embed" data-id="afa17255e9ea4db8a4138102cefeeec3" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Putting a Groovy Twist on Cloud Vision</title><link>https://glaforge.dev/posts/2018/06/18/putting-a-groovy-twist-on-cloud-vision/</link><pubDate>Mon, 18 Jun 2018 20:56:23 +0100</pubDate><guid>https://glaforge.dev/posts/2018/06/18/putting-a-groovy-twist-on-cloud-vision/</guid><description>&lt;p>Powerful machine learning APIs are at your fingertips if you&amp;rsquo;re developing with &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>, as client libraries are available for various programming languages. Today, we&amp;rsquo;re investigating the Cloud Vision API and its Java SDK, using the &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a> programming language&amp;mdash;a multi-faceted language for the Java platform that aims to improve developer productivity thanks to a concise, familiar and easy to learn syntax.&lt;/p>
&lt;p>At &lt;a href="https://gr8conf.eu/">GR8Conf Europe&lt;/a>, in Denmark, the conference dedicated to the &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a> ecosystem, I spoke about the machine learning APIs provided by &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>: Vision, Natural Language, Translate, and Speech (both recognition and synthesis). Since it&amp;rsquo;s a groovy conference, we presented samples and demos using a pretty &lt;a href="http://groovy-lang.org/">Groovy&lt;/a> language. I wanted to share the underlying examples with a wider audience, so here&amp;rsquo;s the first of a series of blog posts covering the demos I presented. I&amp;rsquo;ll start with the Google Cloud Vision API, and I will cover the other APIs in future posts.&lt;/p></description><content:encoded>
<![CDATA[<p>Powerful machine learning APIs are at your fingertips if you&rsquo;re developing with <a href="https://cloud.google.com/">Google Cloud Platform</a>, as client libraries are available for various programming languages. Today, we&rsquo;re investigating the Cloud Vision API and its Java SDK, using the <a href="http://groovy-lang.org/">Apache Groovy</a> programming language&mdash;a multi-faceted language for the Java platform that aims to improve developer productivity thanks to a concise, familiar and easy to learn syntax.</p>
<p>At <a href="https://gr8conf.eu/">GR8Conf Europe</a>, in Denmark, the conference dedicated to the <a href="http://groovy-lang.org/">Apache Groovy</a> ecosystem, I spoke about the machine learning APIs provided by <a href="https://cloud.google.com/">Google Cloud Platform</a>: Vision, Natural Language, Translate, and Speech (both recognition and synthesis). Since it&rsquo;s a groovy conference, we presented samples and demos using a pretty <a href="http://groovy-lang.org/">Groovy</a> language. I wanted to share the underlying examples with a wider audience, so here&rsquo;s the first of a series of blog posts covering the demos I presented. I&rsquo;ll start with the Google Cloud Vision API, and I will cover the other APIs in future posts.</p>
<p>Google Cloud Vision API lets you:</p>
<ul>
<li>Get labels of objects and places from your pictures</li>
<li>Detect faces, with precise location of facial features</li>
<li>Determine if the picture is a particular landmark</li>
<li>Check for inappropriate content</li>
<li>Obtain some image attributes information</li>
<li>Find out if the picture is already available elsewhere on the internet</li>
<li>Detect brand logos</li>
<li>Extract text that appears in your images (OCR)</li>
</ul>
<p>You can try out those features online directly at the Cloud Vision API <a href="https://cloud.google.com/vision/">product page</a>.</p>
<p>Here&rsquo;s the output of a visibly egocentric example that I tried on the product page:</p>
<p><figure>
  <a href="#img-a9bc54568f362b9c680af5a228d9382f">
    <img src="/img/groovy-vision/groovy-vision-13lw1.max-800x800.png"
      alt="/img/groovy-vision/groovy-vision-13lw1.max-800x800.png"
       />
  </a>
  <figcaption>/img/groovy-vision/groovy-vision-13lw1.max-800x800.png</figcaption>
</figure>
<div class="lightbox" id="img-a9bc54568f362b9c680af5a228d9382f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/groovy-vision/groovy-vision-13lw1.max-800x800.png"
    alt="/img/groovy-vision/groovy-vision-13lw1.max-800x800.png"
     />
  <div class="lightbox-caption">/img/groovy-vision/groovy-vision-13lw1.max-800x800.png</div>
</div>
</p>
<p>In this first installment, I focus on two aspects: labels and OCR. But before diving in, I want to illustrate some of the use cases that all of the Vision API features enable, when you want to enhance your apps by integrating the Vision API.</p>
<h2 id="label-detection">Label detection</h2>
<p>What&rsquo;s in the picture? That&rsquo;s what label detection is all about. So for example, if you&rsquo;re showing a picture from your vacation of your family and your dog on the beach, you might get labels like <em>People, Beach, Photograph, Vacation, Fun, Sea, Sky, Sand, Tourism, Summer, Shore, Ocean, Leisure, Coast, Girl, Happiness, Travel, Recreation, Friendship, and Family</em>. Those labels are also accompanied by a percentage confidence score.</p>
<p>Example use cases:</p>
<ul>
<li>If you run a photography website, you might want to let your users search for particular pictures on specific topics. Rather than having someone manually label each and every picture uploaded, you can store labels generated by the API as keywords for your search engine.</li>
<li>If you&rsquo;re building the next friend-finder app for animals, perhaps you want to check that the picture indeed contains a dog. Again, labels will help to tell you if that&rsquo;s the case without manually checking each submitted image.</li>
<li>If you need to find pictures that match a certain theme, you can use the labels to help with automatic categorization.</li>
</ul>
<h2 id="face-detection">Face detection</h2>
<p>The Cloud Vision API can spot faces in your pictures with great precision: it gives you detailed information about the location of the face (with a bounding box), plus the position of each eye, eyebrow, and ear, as well as the nose, lips, mouth, and chin. It also tells you how the face is tilted, and even at what angle!<br />
You can also learn about the sentiment of the person&rsquo;s expression: joy, sorrow, anger, or surprise, for example. In addition, you&rsquo;re told if the face is exposed, blurred, or is sporting  headwear.</p>
<p>Example use cases:</p>
<ul>
<li>You are building a light-hearted app in which users add a mustache or silly hat to uploaded pictures, you now know how to initially position and size the added objects.</li>
<li>If you want to know (or estimate) the number of attendees in a meeting or presentation at a conference, you can use the API to provide a count of the number of faces in the picture.</li>
</ul>
<h2 id="landmark-detection">Landmark detection</h2>
<p>For pictures of famous landmarks, like say, the Eiffel Tower, Buckingham Palace, the Statue of Liberty, or the Golden Gate bridge, the API will tell you which landmark it recognized, and will even give you the GPS location coordinates.</p>
<p>Example use cases:</p>
<ul>
<li>If you want users of your app or site to upload only pictures of a particular location, you could use those coordinates (provided in latitude and longitude) to automatically check that the place photographed is within the right geo-fenced bounding box.</li>
<li>If you want to automatically show pictures on a map in your app, you could take advantage of the GPS coordinates, or automatically enrich tourism websites with pictures from the visitors of specific locales.</li>
</ul>
<h2 id="inappropriate-content-detection">Inappropriate content detection</h2>
<p>Vision API gives you a confidence percentage about different types of potentially inappropriate content in images, including adult content, depictions of violence, medical content (such as surgical procedures or MRIs), and spoofed pictures (with user-added text and marks).</p>
<p>Example use case:</p>
<ul>
<li>If you want to avoid the unwelcome surprise of inappropriate user-generated content showing up in your site or app, you can use this capability to filter images automatically.</li>
</ul>
<h2 id="image-attributes-and-web-annotations">Image attributes and web annotations</h2>
<p>Pictures all have dominant colors, and you can use the Vision API get a sense of which colors are represented in your image and in which proportion. The Cloud Vision API gives you a palette of colors corresponding to your picture.</p>
<p>In addition to color information, it also suggests possible crop hints, so you can crop the picture to different aspect ratios.</p>
<p>You also get information about whether your picture can be found elsewhere on the net, with a list of URLs with matched images as well as full and partial matches.</p>
<p>Beyond the label detection, the API identifies &ldquo;entities&rdquo;, returning to you IDs of those entities from the Google <a href="https://www.google.com/intl/bn/insidesearch/features/search/knowledge.html">Knowledge Graph</a>.</p>
<p>Example use cases:</p>
<ul>
<li>If you want to make your app or website responsive, before loading the full images you may want to show colored placeholders for them. You can get that information with the palette information from the API.</li>
<li>If you&rsquo;d like to automatically crop pictures while keeping their essential aspects, you can use the crop hints.</li>
<li>If you allow photographers to upload their pictures on your website and you want to check that no one is stealing pictures and posting them without proper attribution, you can use the API to check if each picture can be found elsewhere on the web.</li>
<li>For the picture of me in the introduction of this post, Vision API recognized entities like &ldquo;<a href="http://glaforge.appspot.com/">Guillaume Laforge</a>&rdquo; (me!), <a href="http://groovy-lang.org/">Groovy</a> (the programming language I&rsquo;ve been working on since 2003), <a href="https://en.wikipedia.org/wiki/JavaOne">JavaOne</a> (a conference I&rsquo;ve often spoken at), &ldquo;<a href="https://www.manning.com/books/groovy-in-action-second-edition">Groovy in Action</a>&rdquo; (the book I&rsquo;ve co-authored), &ldquo;<a href="https://community.oracle.com/community/java/java-champions">Java Champions</a>&rdquo; (I&rsquo;ve recently been nominated!), and &ldquo;Software Developer&rdquo; (yes, I do code!). Thanks to those entities, you are able to automatically recognize famous people&mdash;more than just me!</li>
</ul>
<h2 id="brand-or-logo-detection">Brand or logo detection</h2>
<p>In addition to text recognition (discussed immediately below), the Vision API tells you if it recognized any logos or brands.</p>
<p>Example use case:</p>
<ul>
<li>If you want your company&rsquo;s brand or products to be displayed on supermarket shelves, you might have people take pictures of those shelves and confirm automatically that your logo is being displayed.</li>
</ul>
<h2 id="ocr-or-text-recognition">OCR or text recognition</h2>
<p>With OCR text detection, you can find text that is displayed in your pictures. Not only does the API gives you the raw text and automatically detects the locale, but you also get all the bounding boxes for the recognized words, as well as a kind of document format, showing the hierarchy of the various blocks of text that appear.</p>
<p>Example use case:</p>
<ul>
<li>When you want to automatically scan expense receipts, enter text rapidly from pictures, or tackle any of the usual use cases for OCR, you can use the API to find and extract any text identified within.</li>
</ul>
<h2 id="time-to-get-groovy">Time to get Groovy!</h2>
<p>Now that I&rsquo;ve provided lots of use cases for where and when you may want to use Vision API, it&rsquo;s time to look at some code, right? So as I said, in this post I&rsquo;ll highlight just two features: label detection and text detection.</p>
<p>Using the <a href="http://groovy-lang.org/">Apache Groovy</a> programming language, I will illustrate two approaches: the first one uses a REST client library like <a href="https://github.com/jwagenleitner/groovy-wslite"><code>groovy-wslite</code></a>, and the second one uses the <a href="https://cloud.google.com/vision/docs/libraries">Java client library</a> provided for the API.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>In order to use Vision API, you&rsquo;ll need to have an account on Google Cloud Platform (GCP). You can benefit from the $300 <a href="https://cloud.google.com/free/">free trial</a> credits, as well as free quotas. For instance, for Vision API, without even consuming your credits, you can make 1000 calls for free every month. You can also take a look at the API&rsquo;s <a href="https://cloud.google.com/vision/pricing">pricing</a> details, once you exceed the quota or your credits.</p>
<p>Briefly, if you&rsquo;re not already using GCP or still don&rsquo;t have an account, please <a href="https://cloud.google.com/getting-started/">register and create one</a>.</p>
<p>Once you&rsquo;ve set up your GCP account, create a cloud project and <a href="https://cloud.google.com/vision/docs/before-you-begin">enable access to Vision API</a> for the project. That&rsquo;s it&mdash;now you&rsquo;re ready to follow the steps detailed below.</p>
<p>Note that after you have enabled the API, you still need to authenticate your client to use the service. There are different possibilities here. In the OCR example, I call the REST API and use an API key passed as a query parameter. In the label detection sample, I use a service account with application default credentials. You can learn more about those approaches in the <a href="https://cloud.google.com/vision/docs/auth">authentication</a> documentation.</p>
<p>Okay, let&rsquo;s get started!</p>
<h2 id="ocr-using-the-vision-rest-endpoint">OCR using the Vision REST endpoint</h2>
<p>During the spring and summer allergy season, many people are interested in which pollens are likely to trigger their allergies. In France (where I live), we have a website that shows a map of the country, and when you hover over your region, it shows you a picture of the active allergens and their levels. However, this is really just a picture with the names of said allergens. So I decided to extract a list of allergens from this image using the Vision API&rsquo;s OCR method:</p>
<p><figure>
  <a href="#img-958c58f79aff641c14669e374f391355">
    <img src="/img/groovy-vision/groovy-vision-2yg2j.max-600x600.png"
      alt="/img/groovy-vision/groovy-vision-2yg2j.max-600x600.png"
       />
  </a>
  <figcaption>/img/groovy-vision/groovy-vision-2yg2j.max-600x600.png</figcaption>
</figure>
<div class="lightbox" id="img-958c58f79aff641c14669e374f391355">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/groovy-vision/groovy-vision-2yg2j.max-600x600.png"
    alt="/img/groovy-vision/groovy-vision-2yg2j.max-600x600.png"
     />
  <div class="lightbox-caption">/img/groovy-vision/groovy-vision-2yg2j.max-600x600.png</div>
</div>
</p>
<p>In Apache Groovy, when calling REST APIs, I often use the <a href="https://github.com/jwagenleitner/groovy-wslite"><code>groovy-wslite</code></a> library. But there are other similar great libraries like <a href="https://http-builder-ng.github.io/http-builder-ng/"><code>HTTPBuilder-NG</code></a>, which offer similar capabilities via a nice syntax too.</p>
<p>To start, let&rsquo;s <a href="http://docs.groovy-lang.org/latest/html/documentation/grape.html">grab</a> the REST client library and instantiate it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;com.github.groovy-wslite:groovy-wslite:1.1.2&#39;</span><span style="color:#666">)</span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">wslite.rest.*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> client <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> RESTClient<span style="color:#666">(</span><span style="color:#4070a0">&#39;https://vision.googleapis.com/v1/&#39;</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>Here&rsquo;s the URL of the picture with the text I&rsquo;m interested in:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> imgUrl <span style="color:#666">=</span> <span style="color:#4070a0">&#34;http://internationalragweedsociety.org/vigilance/d%2094.gif&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> API_KEY <span style="color:#666">=</span> <span style="color:#4070a0">&#39;REPLACE_ME_WITH_REAL_API_KEY&#39;</span>
</span></span></code></pre></div><p>Be sure to change this dummy text for the API key with a proper API key that you can generate from the &ldquo;APIs &amp; services &gt; Credentials&rdquo; section in the cloud console, as explained <a href="https://support.google.com/cloud/answer/6158862?hl=en">here</a>.</p>
<p>Next, I&rsquo;m going to send a post request to the /images:annotate path with the API key as a query parameter. My request is in JSON, and I&rsquo;m using <a href="http://docs.groovy-lang.org/latest/html/documentation/working-with-collections.html">Groovy&rsquo;s nice list and maps syntax</a> to represent that <a href="http://groovy-lang.org/json.html">JSON</a> request, providing the image URL and the feature I want to use (i.e. text detection):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> response <span style="color:#666">=</span> client<span style="color:#666">.</span><span style="color:#4070a0">post</span><span style="color:#666">(</span><span style="color:#002070;font-weight:bold">path:</span> <span style="color:#4070a0">&#39;/images:annotate&#39;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">query:</span> <span style="color:#666">[</span><span style="color:#002070;font-weight:bold">key:</span> API_KEY<span style="color:#666">])</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    type ContentType<span style="color:#666">.</span><span style="color:#4070a0">JSON</span>
</span></span><span style="display:flex;"><span>    json <span style="color:#4070a0">&#34;requests&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#4070a0">&#34;image&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#4070a0">&#34;source&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#4070a0">&#34;imageUri&#34;</span><span style="color:#666">:</span> imgUrl
</span></span><span style="display:flex;"><span>                <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#4070a0">&#34;features&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#4070a0">&#34;type&#34;</span><span style="color:#666">:</span> <span style="color:#4070a0">&#34;TEXT_DETECTION&#34;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>This corresponds to the following JSON payload:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>{ <span style="color:#4070a0">&#34;requests&#34;</span><span style="color:#666">:</span> [{
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;image&#34;</span><span style="color:#666">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;source&#34;</span><span style="color:#666">:</span> { <span style="color:#4070a0">&#34;imageUri&#34;</span><span style="color:#666">:</span> imgUrl }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#4070a0">&#34;features&#34;</span><span style="color:#666">:</span> [
</span></span><span style="display:flex;"><span>        { <span style="color:#4070a0">&#34;type&#34;</span><span style="color:#666">:</span> <span style="color:#4070a0">&#34;TEXT_DETECTION&#34;</span> }
</span></span><span style="display:flex;"><span>    ]}
</span></span><span style="display:flex;"><span>]}
</span></span></code></pre></div><p>Thanks to Apache Groovy&rsquo;s flexible nature, it&rsquo;s then easy to go through the returned JSON payload to get the list and <code>println</code> all the text annotations and their descriptions (which correspond to the recognized text):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>response<span style="color:#666">.</span><span style="color:#4070a0">json</span><span style="color:#666">.</span><span style="color:#4070a0">responses</span><span style="color:#666">[</span><span style="color:#40a070">0</span><span style="color:#666">].</span><span style="color:#4070a0">textAnnotations</span><span style="color:#666">.</span><span style="color:#4070a0">description</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> println it <span style="color:#666">}</span>
</span></span></code></pre></div><p>In spite of the low quality of the image and some odd font kerning, the API was able to find allergens like &ldquo;châtaignier&rdquo; (with the correct circumflex accent) and &ldquo;urticacées&rdquo; (acute accent). On the allergen &ldquo;cupressacées,&rdquo; the diacritical mark was missed though, and a space is intertwined, but the font seems to be adding extra space between some letters.</p>
<h2 id="label-detection-with-the-java-client-library">Label detection with the Java client library</h2>
<p>For my second sample, I was inspired by my visit to Copenhagen for <a href="https://gr8conf.eu/">GR8Conf Europe</a>. I decided to see what labels the API would return for a typical picture of the lovely colorful facades of the <a href="https://en.wikipedia.org/wiki/Nyhavn">Nyhavn</a> harbor.</p>
<p><figure>
  <a href="#img-5a962692cea9e760545aa34aae2fcbba">
    <img src="/img/groovy-vision/groovy-vision-39426.max-800x800.jpeg"
      alt="/img/groovy-vision/groovy-vision-39426.max-800x800.jpeg"
       />
  </a>
  <figcaption>/img/groovy-vision/groovy-vision-39426.max-800x800.jpeg</figcaption>
</figure>
<div class="lightbox" id="img-5a962692cea9e760545aa34aae2fcbba">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/groovy-vision/groovy-vision-39426.max-800x800.jpeg"
    alt="/img/groovy-vision/groovy-vision-39426.max-800x800.jpeg"
     />
  <div class="lightbox-caption">/img/groovy-vision/groovy-vision-39426.max-800x800.jpeg</div>
</div>
</p>
<p>Let&rsquo;s <a href="http://docs.groovy-lang.org/latest/html/documentation/grape.html">grab</a> the Java client library for the vision API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;com.google.cloud:google-cloud-vision:1.24.1&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.cloud.vision.v1.*</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.protobuf.*</span>
</span></span></code></pre></div><p>Here&rsquo;s the URL of the picture:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> imgUrl <span style="color:#666">=</span>
</span></span><span style="display:flex;"><span>   <span style="color:#4070a0">&#34;https://upload.wikimedia.org/wikipedia/commons/3/39/Nyhavn_MichaD.jpg&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#666">.</span><span style="color:#4070a0">toURL</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>Now, let&rsquo;s instantiate the ImageAnnotatorClient class. It&rsquo;s a Closeable object, so we can use Groovy&rsquo;s <code>withCloseable{}</code> method:
<code>ImageAnnotatorClient.create().withCloseable { vision -&gt;</code>
We need the bytes of the picture, which we obtain via the <code>.bytes</code> shortcut, and we create a ByteString object from the <a href="https://developers.google.com/protocol-buffers/">protobuf</a> library used by the Vision API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> imgBytes <span style="color:#666">=</span> ByteString<span style="color:#666">.</span><span style="color:#4070a0">copyFrom</span><span style="color:#666">(</span>imgUrl<span style="color:#666">.</span><span style="color:#4070a0">bytes</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>To create the request with the AnnotateImageRequest builder, we can employ the Groovy <a href="http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Object.html#tap(groovy.lang.Closure)"><code>tap{}</code></a> method to simplify usage of the builder design pattern:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> request <span style="color:#666">=</span> AnnotateImageRequest<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">().</span><span style="color:#4070a0">tap</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    addFeatures Feature<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>                       <span style="color:#666">.</span><span style="color:#4070a0">setType</span><span style="color:#666">(</span>Feature<span style="color:#666">.</span><span style="color:#4070a0">Type</span><span style="color:#666">.</span><span style="color:#4070a0">LABEL_DETECTION</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                       <span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    setImage Image<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#666">.</span><span style="color:#4070a0">setContent</span><span style="color:#666">(</span>imgBytes<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>In that request, we ask for the label detection feature and pass the image bytes. Next, we call the API with our request and then iterate over the resulting label annotations and their confidence score:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>vision<span style="color:#666">.</span><span style="color:#4070a0">batchAnnotateImages</span><span style="color:#666">([</span>request<span style="color:#666">]).</span><span style="color:#4070a0">responsesList</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> res <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>res<span style="color:#666">.</span><span style="color:#4070a0">hasError</span><span style="color:#666">())</span> println <span style="color:#4070a0">&#34;Error: ${res.error.message}&#34;</span>
</span></span><span style="display:flex;"><span>    res<span style="color:#666">.</span><span style="color:#4070a0">labelAnnotationsList</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> annotation <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>        println <span style="color:#4070a0">&#34;${annotation.description.padLeft(20)} (${annotation.score})&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>The labels found (and their confidence) are as follows:</p>
<pre tabindex="0"><code>                waterway (0.97506875)
    water transportation (0.9240114)
                    town (0.9142202)
                   canal (0.8753313)
                    city (0.86910504)
                   water (0.82833123)
                  harbor (0.82821053)
                 channel (0.73568773)
                     sky (0.73083687)
                building (0.6117833)
</code></pre><p>That looks pretty accurate to me!</p>
<h2 id="conclusion">Conclusion</h2>
<p>There are tons of situations where you can benefit from <a href="https://cloud.google.com/vision/">Google Cloud Vision</a>&rsquo;s simple and effective image analysis and your use of the API becomes even groovier when using the <a href="http://groovy-lang.org/">Apache Groovy</a> language! Be sure to try out the API directly from the website with the built-in demo, and get started with our <a href="https://codelabs.developers.google.com/codelabs/cloud-vision-intro/index.html?index=..%2F..%2Findex#0">Cloud Vision codelabs</a>.</p>
<p>If you want to go further, I also encourage you to also have a look at the alpha version of <a href="https://cloud.google.com/automl/">Cloud AutoML Vision</a>: you can extend the Vision API by training it on your own picture dataset. By doing so, you can let Cloud Vision recognize particular objects or elements in your photos with finer-grained labels that are specific to your needs.</p>
<p>Upcoming installments in this series will cover natural language understanding (NLU), text translation, speech recognition, and voice synthesis. So stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Automating Chrome Headless mode on App Engine with Node.JS 8</title><link>https://glaforge.dev/posts/2018/06/12/automating-chrome-headless-mode-on-app-engine-with-node-js-8/</link><pubDate>Tue, 12 Jun 2018 13:43:51 +0100</pubDate><guid>https://glaforge.dev/posts/2018/06/12/automating-chrome-headless-mode-on-app-engine-with-node-js-8/</guid><description>&lt;p>On the &lt;a href="http://cloud.google.com/">Google Cloud&lt;/a> front today, the big news is the release of the &lt;a href="https://cloudplatform.googleblog.com/2018/06/Now-you-can-deploy-your-Node-js-app-to-App-Engine-standard-environment.html">new Node.JS 8 runtime for Google App Engine Standard&lt;/a>. It&amp;rsquo;s been a while since a completely new runtime was added to the list of supported platforms (Python, Java, PHP, Go). You could already run anything in custom containers on App Engine Flex, including your own containerized Node app, but now you can have all the nice developer experience on the Standard environment, with fast deployment times, and 0 to 1 to n instance automatic scaling (you can see the difference between those two environments &lt;a href="https://cloud.google.com/appengine/docs/nodejs/">here&lt;/a>).&lt;/p></description><content:encoded>
<![CDATA[<p>On the <a href="http://cloud.google.com/">Google Cloud</a> front today, the big news is the release of the <a href="https://cloudplatform.googleblog.com/2018/06/Now-you-can-deploy-your-Node-js-app-to-App-Engine-standard-environment.html">new Node.JS 8 runtime for Google App Engine Standard</a>. It&rsquo;s been a while since a completely new runtime was added to the list of supported platforms (Python, Java, PHP, Go). You could already run anything in custom containers on App Engine Flex, including your own containerized Node app, but now you can have all the nice developer experience on the Standard environment, with fast deployment times, and 0 to 1 to n instance automatic scaling (you can see the difference between those two environments <a href="https://cloud.google.com/appengine/docs/nodejs/">here</a>).</p>
<p>To play with this new runtime, I decided to follow the steps in this guide about <a href="https://cloud.google.com/appengine/docs/standard/nodejs/using-headless-chrome-with-puppeteer">using Chrome headless with Puppeteer</a>.</p>
<p>As my readers know, I&rsquo;m not really a Node person, and usually dabble more with <a href="http://groovy-lang.org/">Apache Groovy</a> and Java, but this runtime was interesting to me as there&rsquo;s a nice integration with native packages. Let me explain.</p>
<p>The App Engine Node runtime <a href="https://cloud.google.com/appengine/docs/standard/nodejs/reference/system-packages">includes tons of native package out of the box</a>, without requiring you to install anything (except the Node modules that take advantage of those packages, of course.) For instance, if you need to do any audio / video manipulation, there&rsquo;s an ffmpeg package. If you want to deal with Git repositories, there&rsquo;s a git package. Need to manipulate images, there&rsquo;s ImageMagick, etc. And there are usually nice Node wrapper modules around those native components.</p>
<p>Among those system pre-installed packages, there&rsquo;s all the necessary ones to run <a href="https://developers.google.com/web/updates/2017/04/headless-chrome">Headless Chrome</a>, ie. running the Chrome browser but without displaying its window basically.</p>
<p>Furthermore, there&rsquo;s the <a href="https://developers.google.com/web/tools/puppeteer/">Puppeteer</a> Node module, which is a library to control Chrome. With those two, you can completely automate the usage of Chrome on the server-side.</p>
<p>What can you do with that? Well, you can:</p>
<ul>
<li>look at / introspect / manipulate the DOM,</li>
<li>pre-render content for your single page apps,</li>
<li>take screenshots of web pages,</li>
<li>watch a particular page and compute diffs between different versions, etc.</li>
</ul>
<h2 id="lets-get-started">Let&rsquo;s get started!</h2>
<p>Without blindly recopying all the steps explained in the tutorial for <a href="https://cloud.google.com/appengine/docs/standard/nodejs/using-headless-chrome-with-puppeteer">running Chrome headless</a>, I&rsquo;ll simply highlight some of key points. The goal is to let puppeteer take screenshots of webpages.</p>
<p>In your package.json, you need the reference the puppeteer module, and potentially express for handling your web requests:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>  <span style="color:#4070a0">&#34;dependencies&#34;</span><span style="">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;express&#34;</span>: <span style="color:#4070a0">&#34;^4.16.3&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;puppeteer&#34;</span>: <span style="color:#4070a0">&#34;^1.2.0&#34;</span>
</span></span><span style="display:flex;"><span>  }<span style="">,</span>
</span></span></code></pre></div><p>Taking advantage of Node 8&rsquo;s async capabilities, in your <code>app.js</code> file, you can instantiate puppeteer:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> browser <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> puppeteer.launch({
</span></span><span style="display:flex;"><span>  args<span style="color:#666">:</span> [<span style="color:#4070a0">&#39;--no-sandbox&#39;</span>, <span style="color:#4070a0">&#39;--disable-setuid-sandbox&#39;</span>]
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Then navigate to the desired page:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> page <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> browser.newPage();
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">await</span> page.<span style="color:#007020;font-weight:bold">goto</span>(url);
</span></span></code></pre></div><p>Take a screenshot and render it back to the browser:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">const</span> imageBuffer <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">await</span> page.screenshot();
</span></span><span style="display:flex;"><span>  res.set(<span style="color:#4070a0">&#39;Content-Type&#39;</span>, <span style="color:#4070a0">&#39;image/png&#39;</span>);
</span></span><span style="display:flex;"><span>  res.send(imageBuffer);
</span></span></code></pre></div><p>To deploy to the Node runtime, you also need the app.yaml deployment descriptor:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">runtime</span>:<span style="color:#bbb"> </span>nodejs8<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">instance_class</span>:<span style="color:#bbb"> </span>F4_1G<span style="color:#bbb">
</span></span></span></code></pre></div><p>We specify that we want to use the new node runtime, but also that we want a slightly bigger instance to run our Node app, as Chrome is pretty hungry with RAM!</p>
<p>Then deploy your app with the <a href="https://cloud.google.com/sdk/gcloud/">gcloud CLI</a>.</p>
<p>Be sure to check the <a href="https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/appengine/headless-chrome">whole code on Github</a> for all the details.</p>
<p>One quick remark: although it&rsquo;s not mentioned in the tutorial, when you&rsquo;ll first try to deploy the application, it&rsquo;ll tell you that you need to enable the Container Builder API. The error message will be something like <code>Container Builder has not been used in project xyz before or it is disabled. Enable it by visiting...</code> You just need to follow the indicated URL to enable Container Builder. Container Builder is responsible for containerizing your application to be run on App Engine.</p>
<p>Then I was able to navigate to my app, pass it a URL, and get back the screenshot of the web page at that URL. It&rsquo;s pretty handy if you want to integrate thumbnails of websites you reference in your blog posts, for example, or if you want to see if there are differences between different versions of a web page (for integration testing purposes).</p>
<h2 id="conclusion">Conclusion</h2>
<p>The Java ecosystem has a wealth of libraries for various tasks, but often, there are native libraries which are more fully-featured, and Node generally provides nice wrappers for them. Chrome headless with Puppeteer is one example, but ImageMagick for image manipulation is another great one, where I could not find a good equivalent library in the Java ecosystem. So as they say, use the best tool for the job! In the age of microservices, feel free to use another tech stack that best fit the task at hand. And it&rsquo;s really exciting to see this <a href="https://cloudplatform.googleblog.com/2018/06/Now-you-can-deploy-your-Node-js-app-to-App-Engine-standard-environment.html">new Node 8 runtime for App Engine</a> now being available so that you can take advantage of it in your projects.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Vision recognition with a Groovy twist</title><link>https://glaforge.dev/posts/2018/06/06/vision-recognition-with-a-groovy-twist/</link><pubDate>Wed, 06 Jun 2018 14:05:23 +0100</pubDate><guid>https://glaforge.dev/posts/2018/06/06/vision-recognition-with-a-groovy-twist/</guid><description>&lt;p>Last week at &lt;a href="https://gr8conf.eu/">GR8Conf Europe&lt;/a>, I spoke about the machine learning APIs provided by 
&lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>: Vision, Natural Language, Speech recognition and synthesis, etc.
Since it&amp;rsquo;s GR8Conf, that means showing samples and demos using a pretty &lt;a href="http://groovy-lang.org/">Groovy&lt;/a> language,
and I promised to share my code afterwards. So here&amp;rsquo;s a series of blog posts covering the demos I&amp;rsquo;ve presented.
We&amp;rsquo;ll start with the Vision API.&lt;/p>
&lt;p>The Vision API allows you to:&lt;/p>
&lt;ul>
&lt;li>Get labels of what appears in your pictures,&lt;/li>
&lt;li>Detect faces, with precise location of face features,&lt;/li>
&lt;li>Tell you if the picture is a particular landmark,&lt;/li>
&lt;li>Check for inappropriate content,&lt;/li>
&lt;li>Give you some image attributes information,&lt;/li>
&lt;li>Find if the picture is already available on the net,&lt;/li>
&lt;li>Detects brand logos,&lt;/li>
&lt;li>Or extract text that appears in your images (OCR).&lt;/li>
&lt;/ul>
&lt;p>You can try out those features online directly from the Cloud Vision API product page:&lt;/p></description><content:encoded>
<![CDATA[<p>Last week at <a href="https://gr8conf.eu/">GR8Conf Europe</a>, I spoke about the machine learning APIs provided by 
<a href="https://cloud.google.com/">Google Cloud Platform</a>: Vision, Natural Language, Speech recognition and synthesis, etc.
Since it&rsquo;s GR8Conf, that means showing samples and demos using a pretty <a href="http://groovy-lang.org/">Groovy</a> language,
and I promised to share my code afterwards. So here&rsquo;s a series of blog posts covering the demos I&rsquo;ve presented.
We&rsquo;ll start with the Vision API.</p>
<p>The Vision API allows you to:</p>
<ul>
<li>Get labels of what appears in your pictures,</li>
<li>Detect faces, with precise location of face features,</li>
<li>Tell you if the picture is a particular landmark,</li>
<li>Check for inappropriate content,</li>
<li>Give you some image attributes information,</li>
<li>Find if the picture is already available on the net,</li>
<li>Detects brand logos,</li>
<li>Or extract text that appears in your images (OCR).</li>
</ul>
<p>You can try out those features online directly from the Cloud Vision API product page:</p>
<p><a href="https://cloud.google.com/vision/">https://cloud.google.com/vision/</a></p>
<p>Here&rsquo;s a selfish example output:</p>
<p><figure>
  <a href="#img-ed743079135cdf5928b913d8fbcd8116">
    <img src="/img/vision-groovy/ml-apis-01-try-out&#43;small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ed743079135cdf5928b913d8fbcd8116">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/vision-groovy/ml-apis-01-try-out&#43;small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In this first installment, I&rsquo;ll focus on two aspects: labels and OCR. But before diving, I wanted to illustrate some of the <strong>use cases</strong> that those features enable, when you want to boost your apps by integrating the Vision API.</p>
<h2 id="label-detection">Label detection</h2>
<p>What&rsquo;s in the picture? That&rsquo;s what label detection is all about. So for example, if you&rsquo;re showing a picture from your vacations with your family and dog on the beach, you might be getting labels like <em>&ldquo;People, Beach, Photograph, Vacation, Fun, Sea, Sky, Sand, Tourism, Summer, Shore, Ocean, Leisure, Coast, Girl, Happiness, Travel, Recreation, Friendship, Family&rdquo;</em>. Those labels are also accompanied by percentage confidence.</p>
<h3 id="example-use-cases">Example use cases:</h3>
<ul>
<li>If your website is a photography website, you might want your users to be able to search for particular pictures on specific topics. Rather than having someone manually label each and every picture uploaded, you can store those labels as keywords for your search engine.</li>
<li>You&rsquo;re building the next Instagram for animals, perhaps you want to check that the picture indeed contains a dog in it. Again labels will help to tell you if that&rsquo;s the case or not.</li>
<li>Those labels can help with automatic categorization as well, so you can more easily find pictures along certain themes.</li>
</ul>
<h2 id="face-detection">Face detection</h2>
<p>Vision API can spot faces in your pictures with great precision, as it gives you detailed information about the location of the face (with a bounding box), as well as the position of each eye, eyebrows, nose, lips / mouth, ear, or chin. It also tells you how your face is tilted, even with which angle!</p>
<p>You can also learn about the sentiment of the person: joy, sorrow, anger, surprise. In addition, you&rsquo;re told if the face is exposed, blurred, or has some headwear.</p>
<h3 id="example-use-cases-1">Example use cases:</h3>
<ul>
<li>Snapchat-style, you want to add a mustache or silly hat to pictures uploaded by your users.</li>
<li>Another handy aspect is that you can also count the number of faces in a picture. For example, if you want to count the number of attendees in a meeting or presentation at a conference, you&rsquo;d have a good estimate of people with the number of faces recognized.</li>
</ul>
<h2 id="landmark-detection">Landmark detection</h2>
<p>If the picture is of a famous landmark, like say, the Eiffel Tower, Buckingham Palace, the Statue of Liberty, the Golden Gate, the API will tell you which landmark it recognized, and will even give you the GPS coordinates of the place.</p>
<h3 id="example-use-cases-2">Example use cases:</h3>
<ul>
<li>Users of your app or site should only upload pictures of a particular location, you could use those coordinates to automatically check that the place photographed is within the right geo-fenced bounding box.</li>
<li>You want to automatically show pictures on a map on your app, you could take advantage of the GPS coordinates, or automatically enrich a tourism websites with pictures from the visitors of that place.</li>
</ul>
<h2 id="inappropriate-content-detection">Inappropriate content detection</h2>
<p>Vision API will give you a percentage confidence about whether the picture contains adult content, is a spoofed picture (with user added text and marks), bears some medical character, displays violence or racy materials.</p>
<h3 id="example-use-case">Example use case:</h3>
<ul>
<li>The main use case here is indeed to be able to filter images automatically, to avoid any bad surprise of user-generated content showing up in your site or app when it shouldn&rsquo;t.</li>
</ul>
<h2 id="image-attributes-and-web-annotations">Image attributes and web annotations</h2>
<p>Pictures all have dominant colors, and you can get a sense of which colors are indeed representing the best your image,
in which proportion. Vision API gives you a palette of colors corresponding to your picture.</p>
<p>In addition to color information, it also suggests possible crop hints, so you could crop the picture to different aspect ratios.</p>
<p>You get information about whether this picture can be seen elsewhere on the net,
with a list of URL with matched images, full matches, or partial matches.</p>
<p>Beyond the label detection, the API identifies &ldquo;entities&rdquo;, returning to you IDs of those entities
from the Google <a href="https://www.google.com/intl/bn/insidesearch/features/search/knowledge.html">Knowledge Graph</a>.</p>
<h3 id="example-use-cases-3">Example use cases:</h3>
<ul>
<li>To make your app or website responsive, before loading the full images, you&rsquo;d like to show colored placeholders for the picture. You can get that information with the palette information returned.</li>
<li>You&rsquo;d like to automatically crop pictures to keep the essential aspects of it.</li>
<li>Photographers upload their pictures on your website, but you want to ensure that no one steals those pictures and put them on the net without proper attribution. You can find out whether this picture can be seen elsewhere on the web.</li>
<li>For the picture of me above, Vision API recognized entities like &ldquo;Guillaume Laforge&rdquo; (me!), Groovy (the programming language I&rsquo;ve been working on since 2003), JavaOne (a conference I&rsquo;ve often spoken at), &ldquo;Groovy in Action&rdquo; (the book I&rsquo;ve co-authored), &ldquo;Java Champions&rdquo; (I&rsquo;ve recently been nominated!), or &ldquo;Software Developer&rdquo; (yes, I still code a bit)</li>
</ul>
<h2 id="brand--logo-detection">Brand / logo detection</h2>
<p>In addition to text recognition, that we&rsquo;ll mention right below, Vision API tells you if it recognized some logos or brands.</p>
<h3 id="example-use-case-1">Example use case:</h3>
<ul>
<li>If you&rsquo;re a brand, have some particular products, and you want your brand or products to be displayed on supermarket shelves, you might have people take pictures of those shelves and confirm automatically if your logo is displayed or not.</li>
</ul>
<h3 id="ocr--text-recognition">OCR / text recognition</h3>
<p>You can find the text that is displayed on your pictures. Not only does it gives you the raw text, but you also get all the bounding boxes for the recognized words, as well as offers a kind of document format, showing the various blocks of texts that appear.</p>
<h3 id="example-use-case-2">Example use case:</h3>
<ul>
<li>You want to automatically scan expense receipts, enter text rapidly from pictures, etc. The usual use cases for OCR!</li>
</ul>
<h2 id="time-to-get-groovy">Time to get Groovy!</h2>
<p>People often wonder where and when they can use the Vision API, I think I&rsquo;ve given enough use cases for now, with detailed explanations. But it&rsquo;s time to show some code, right? So as I said, I&rsquo;ll highlight just two features: label detection and text detection.</p>
<p>Using the Apache Groovy programming language, I wanted to illustrate two approaches: the first one using the a REST client like <a href="https://github.com/jwagenleitner/groovy-wslite">groovy-wslite</a>, and the second one just using the <a href="https://cloud.google.com/vision/docs/libraries">Java SDK</a> provided for the API.</p>
<h3 id="preliminary">Preliminary</h3>
<p>In order to use the Vision API, you&rsquo;ll need to have created an account on Google Cloud Platform (GCP for short).
You can benefit from the $300 of credits of the <a href="https://cloud.google.com/free/">free trial</a>, as well as free quotas.
For instance, for the Vision API, without even using your credits, you can make 1000 calls for free every month.
You can also have a look at the <a href="https://cloud.google.com/vision/pricing">pricing</a> of the API, once you go above the quota or your credits.</p>
<p>Briefly, if you&rsquo;re not already using GCP or have an account on it, please register and create your account on GCP,
then create a cloud project. Once the project is created, 
<a href="https://cloud.google.com/vision/docs/before-you-begin">enable access to Vision API</a>,
and you&rsquo;re ready to follow the steps detailed hereafter.</p>
<p>Although we&rsquo;ve enabled the API, we still need somehow to be authenticated to use that service.
There are different possibilities here.
In the first sample with the OCR example, I&rsquo;m calling the REST API and will be using an API key passed as query parameter,
whereas for my label detection sample, I&rsquo;m using a service account with applicaction default credentials.
You can learn more about those approaches in the documentation on <a href="https://cloud.google.com/vision/docs/auth">authentication</a>.</p>
<p>Okay, let&rsquo;s get started!</p>
<h3 id="ocr-calling-the-vision-rest-endpoint">OCR calling the Vision REST endpoint</h3>
<p>With spring and summer, allergetic people might be interested in which pollens are going to cause their allergies to bother them. Where I live, in France, there&rsquo;s a website showing a map of the country, and you can hover the region where you are, and see a picture of the allergens and their levels. However, this is really just a picture with the names of said allergens. So I decided to get their list with the Vision API.</p>
<p><figure>
  <a href="#img-c1f00fb33bc4561b4fe2b2e029673a22">
    <img src="/img/vision-groovy/ml-apis-03-allergens-labels&#43;small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c1f00fb33bc4561b4fe2b2e029673a22">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/vision-groovy/ml-apis-03-allergens-labels&#43;small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In Apache Groovy, when calling REST APIs, I often use the <a href="https://github.com/jwagenleitner/groovy-wslite">groovy-wslite</a> library. But there are other similar great libraries like <a href="https://http-builder-ng.github.io/http-builder-ng/">HTTPBuilder-NG</a>, which offer similar capabilities with nice syntax too.</p>
<p>Let&rsquo;s <a href="http://docs.groovy-lang.org/latest/html/documentation/grape.html">grab</a> the REST client library and instantiate it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;com.github.groovy-wslite:groovy-wslite:1.1.2&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">wslite.rest.*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> client <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> RESTClient<span style="color:#666">(</span><span style="color:#4070a0">&#39;https://vision.googleapis.com/v1/&#39;</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>Here&rsquo;s the URL of the picture whose text I&rsquo;m interested in:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> imgUrl <span style="color:#666">=</span> <span style="color:#4070a0">&#34;http://internationalragweedsociety.org/vigilance/d%2094.gif&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> API_KEY <span style="color:#666">=</span> <span style="color:#4070a0">&#39;REPLACE_ME_WITH_REAL_API_KEY&#39;</span>
</span></span></code></pre></div><p>Then I&rsquo;m going to send a post request to the <code>/images:annotate</code> path with the API key as query parameter.
My request is in JSON, and I&rsquo;m using Groovy&rsquo;s nice list &amp; maps syntax to represent that JSON request,
providing the image URL and the feature I&rsquo;m interested in (ie. text detection):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> response <span style="color:#666">=</span> client<span style="color:#666">.</span><span style="color:#4070a0">post</span><span style="color:#666">(</span><span style="color:#002070;font-weight:bold">path:</span> <span style="color:#4070a0">&#39;/images:annotate&#39;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">query:</span> <span style="color:#666">[</span><span style="color:#002070;font-weight:bold">key:</span> API_KEY<span style="color:#666">])</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    type ContentType<span style="color:#666">.</span><span style="color:#4070a0">JSON</span>
</span></span><span style="display:flex;"><span>    json <span style="color:#4070a0">&#34;requests&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#4070a0">&#34;image&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#4070a0">&#34;source&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#4070a0">&#34;imageUri&#34;</span><span style="color:#666">:</span> imgUrl
</span></span><span style="display:flex;"><span>                <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">],</span>
</span></span><span style="display:flex;"><span>            <span style="color:#4070a0">&#34;features&#34;</span><span style="color:#666">:</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#4070a0">&#34;type&#34;</span><span style="color:#666">:</span> <span style="color:#4070a0">&#34;TEXT_DETECTION&#34;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>This corresponds to the following JSON payload:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;requests&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;image&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&#34;source&#34;</span>: {
</span></span><span style="display:flex;"><span>                    <span style="color:#062873;font-weight:bold">&#34;imageUri&#34;</span>: <span style="">imgUrl</span>
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;features&#34;</span>: [
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;TEXT_DETECTION&#34;</span>
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Thanks to Groovy&rsquo;s flexible nature, it&rsquo;s then easy to go through the returned JSON payload to get the list and println all the text annotations and their description (which corresponds to the recognized text):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>println response<span style="color:#666">.</span><span style="color:#4070a0">json</span><span style="color:#666">.</span><span style="color:#4070a0">responses</span><span style="color:#666">[</span><span style="color:#40a070">0</span><span style="color:#666">].</span><span style="color:#4070a0">textAnnotations</span><span style="color:#666">.</span><span style="color:#4070a0">description</span>
</span></span></code></pre></div><h3 id="label-detection-with-the-java-client-library">Label detection with the Java client library</h3>
<p>For my second sample, as I visited Copenhagen for <a href="https://gr8conf.eu/">GR8Conf Europe</a>,
I decided to see what labels the API would return for a typical picture of the lovely colorful facades of the 
<a href="https://en.wikipedia.org/wiki/Nyhavn">Nyhavn</a> harbor.</p>
<p><figure>
  <a href="#img-5488655e03d848109ff999212eb51f43">
    <img src="/img/vision-groovy/ml-apis-02-nyhavn&#43;small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-5488655e03d848109ff999212eb51f43">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/vision-groovy/ml-apis-02-nyhavn&#43;small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Let&rsquo;s <a href="http://docs.groovy-lang.org/latest/html/documentation/grape.html">grab</a> the Java client library for the vision API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;com.google.cloud:google-cloud-vision:1.24.1&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.cloud.vision.v1.*</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.protobuf.*</span>
</span></span></code></pre></div><p>Here&rsquo;s the URL of the picture:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> imgUrl <span style="color:#666">=</span> <span style="color:#4070a0">&#34;https://upload.wikimedia.org/wikipedia/commons/3/39/Nyhavn_MichaD.jpg&#34;</span> <span style="color:#666">.</span><span style="color:#4070a0">toURL</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>Let&rsquo;s instantiate the ImageAnnotatorClient class. It&rsquo;s a closeable object, so we can use Groovy&rsquo;s withCloseable{} method:</p>
<pre tabindex="0"><code>ImageAnnotatorClient.create().withCloseable { vision -&gt;
</code></pre><p>We need the bytes of the picture, that we obtain with the <code>.bytes</code> shortcut, and we create a <code>ByteString</code> object from the <a href="https://developers.google.com/protocol-buffers/">protobuf</a> library used by the Vision API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> imgBytes <span style="color:#666">=</span> ByteString<span style="color:#666">.</span><span style="color:#4070a0">copyFrom</span><span style="color:#666">(</span>imgUrl<span style="color:#666">.</span><span style="color:#4070a0">bytes</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>Now it&rsquo;s time to create our request, with the @AnnotateImageRequest` builder,
using Groovy&rsquo;s <a href="http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Object.html#tap(groovy.lang.Closure)">tap{}</a> 
method to make the use of the builder pattern easier:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> request <span style="color:#666">=</span> AnnotateImageRequest<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">().</span><span style="color:#4070a0">tap</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    addFeatures Feature<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">().</span><span style="color:#4070a0">setType</span><span style="color:#666">(</span>Feature<span style="color:#666">.</span><span style="color:#4070a0">Type</span><span style="color:#666">.</span><span style="color:#4070a0">LABEL_DETECTION</span><span style="color:#666">).</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    setImage    Image<span style="color:#666">.</span><span style="color:#4070a0">newBuilder</span><span style="color:#666">().</span><span style="color:#4070a0">setContent</span><span style="color:#666">(</span>imgBytes<span style="color:#666">).</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>In that request, we ask for the label detection feature, and pass the image bytes.
Then it&rsquo;s time to call the API with our request, and iterate over the resulting label annotations and their confidence score:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>vision<span style="color:#666">.</span><span style="color:#4070a0">batchAnnotateImages</span><span style="color:#666">([</span>request<span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>      <span style="color:#666">.</span><span style="color:#4070a0">responsesList</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> res <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>res<span style="color:#666">.</span><span style="color:#4070a0">hasError</span><span style="color:#666">())</span>
</span></span><span style="display:flex;"><span>        println <span style="color:#4070a0">&#34;Error: ${res.error.message}&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    res<span style="color:#666">.</span><span style="color:#4070a0">labelAnnotationsList</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> annotation <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>        println <span style="color:#4070a0">&#34;${annotation.description.padLeft(20)} (${annotation.score})&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>The labels found (and their confidence) are the following:</p>
<pre tabindex="0"><code>            waterway (0.97506875)
water transportation (0.9240114)
                town (0.9142202)
               canal (0.8753313)
                city (0.86910504)
               water (0.82833123)
              harbor (0.82821053)
             channel (0.73568773)
                 sky (0.73083687)
            building (0.6117833)
</code></pre><p>Pretty good job!</p>
<h2 id="conclusion">Conclusion</h2>
<p>First of all, there are tons of situations where you can benefit from image recognition thanks to <a href="https://cloud.google.com/vision/">Google Cloud Vision</a>. Secondly, it can get even groovier when using the <a href="http://groovy-lang.org/">Apache Groovy</a> language!</p>
<p>In the upcoming installment, we&rsquo;ll speak about natural language understanding, text translation, speech recognition and voice synthesis. So stay tuned!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Machine learning APIs and AI panel discussion at QCon</title><link>https://glaforge.dev/talks/2018/06/04/machine-learning-apis-and-ai-panel-discussion-at-qcon/</link><pubDate>Mon, 04 Jun 2018 16:37:09 +0100</pubDate><guid>https://glaforge.dev/talks/2018/06/04/machine-learning-apis-and-ai-panel-discussion-at-qcon/</guid><description>&lt;p>Last March, I had the chance to attend and speak at &lt;a href="https://qconlondon.com/london2018/schedule/london2018/tabular.html">QCon London&lt;/a>. I spoke at the event for its first edition, many moons prior, so it was fun coming back and seeing how the conference evolved. &lt;/p>
&lt;p>This year, &lt;a href="https://twitter.com/erichoresnyi">Eric Horesnyi&lt;/a> of &lt;a href="https://streamdata.io/">Streamdata&lt;/a> 
was leading the Artificial Intelligence track, and invited me to speak about Machine Learning.&lt;/p>
&lt;p>First, I gave an overview of the Machine Learning offering, from the off-the-shelf ready-made APIs like 
&lt;a href="https://cloud.google.com/vision/">Vision&lt;/a>, &lt;a href="https://cloud.google.com/speech-to-text/">Speech&lt;/a>, 
&lt;a href="https://cloud.google.com/natural-language/">Natural Language&lt;/a>, &lt;a href="https://cloud.google.com/video-intelligence/">Video Intelligence&lt;/a>.
I also mentioned &lt;a href="https://cloud.google.com/automl/">AutoML&lt;/a>,
to further train existing models like the Vision model in order to recognize your own specific details in pictures.
For chatbots, I also covered &lt;a href="https://dialogflow.com/">Dialogflow&lt;/a>.
And I said a few words about Tensorflow and &lt;a href="https://cloud.google.com/ml-engine/">Cloud Machine Learning Engine&lt;/a> 
for training &amp;amp; running your &lt;a href="https://www.tensorflow.org/">Tensorflow&lt;/a> models in the cloud.
You can watch the video by clicking on the picture below:&lt;/p></description><content:encoded>
<![CDATA[<p>Last March, I had the chance to attend and speak at <a href="https://qconlondon.com/london2018/schedule/london2018/tabular.html">QCon London</a>. I spoke at the event for its first edition, many moons prior, so it was fun coming back and seeing how the conference evolved. </p>
<p>This year, <a href="https://twitter.com/erichoresnyi">Eric Horesnyi</a> of <a href="https://streamdata.io/">Streamdata</a> 
was leading the Artificial Intelligence track, and invited me to speak about Machine Learning.</p>
<p>First, I gave an overview of the Machine Learning offering, from the off-the-shelf ready-made APIs like 
<a href="https://cloud.google.com/vision/">Vision</a>, <a href="https://cloud.google.com/speech-to-text/">Speech</a>, 
<a href="https://cloud.google.com/natural-language/">Natural Language</a>, <a href="https://cloud.google.com/video-intelligence/">Video Intelligence</a>.
I also mentioned <a href="https://cloud.google.com/automl/">AutoML</a>,
to further train existing models like the Vision model in order to recognize your own specific details in pictures.
For chatbots, I also covered <a href="https://dialogflow.com/">Dialogflow</a>.
And I said a few words about Tensorflow and <a href="https://cloud.google.com/ml-engine/">Cloud Machine Learning Engine</a> 
for training &amp; running your <a href="https://www.tensorflow.org/">Tensorflow</a> models in the cloud.
You can watch the video by clicking on the picture below:</p>
<p><a href="https://www.infoq.com/presentations/google-ml-services"><figure>
  <a href="#img-3da3042e2c59346eb27db051dbd29c98">
    <img src="/img/qcon-2018/qcon-ml-talk.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3da3042e2c59346eb27db051dbd29c98">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/qcon-2018/qcon-ml-talk.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<p>Eric hosted a panel discussion with all the speakers in the AI track, where we discussed many interesting topics,
to demystify AI and answer questions from the audience. Click on the picture below to watch the panel discussion:</p>
<p><a href="https://www.infoq.com/presentations/ai-panel"><figure>
  <a href="#img-68ae417a84772fc7e20bceb20fd674fc">
    <img src="/img/qcon-2018/qcon-ai-panel.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-68ae417a84772fc7e20bceb20fd674fc">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/qcon-2018/qcon-ai-panel.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Getting started with Groovy technologies on Google Cloud Platform</title><link>https://glaforge.dev/talks/2018/06/01/getting-started-with-groovy-technologies-on-google-cloud-platform/</link><pubDate>Fri, 01 Jun 2018 17:25:16 +0100</pubDate><guid>https://glaforge.dev/talks/2018/06/01/getting-started-with-groovy-technologies-on-google-cloud-platform/</guid><description>&lt;p>Back to &lt;a href="https://gr8conf.eu/">GR8Conf Europe&lt;/a> in Denmark, for the yearly &lt;a href="http://www.groovy-lang.org/">Groovy&lt;/a> community reunion! I had the chance to present two talks.&lt;/p>
&lt;p>The first one on Google&amp;rsquo;s &lt;a href="https://cloud.google.com/products/machine-learning/">Machine Learning APIs&lt;/a>, with samples in Groovy using vision recognition, speech recognition &amp;amp; generation, natural language analysis. I&amp;rsquo;ll come back on ML in Groovy in forthcoming articles.&lt;/p>
&lt;p>And the second talk was an overview of Google Cloud Platform, focusing on the compute and storage options, with demos using Groovy frameworks (&lt;a href="https://ratpack.io/">Ratpack&lt;/a>, &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a>, and the newly released &lt;a href="http://micronaut.io/">Micronaut&lt;/a>) and how to deploy apps on Compute Engine, Kubernetes Engine, App Engine. I&amp;rsquo;ll also come back in further articles on those demos, but in the meantime, I wanted to share my slide deck with you all! Without further ado, here&amp;rsquo;s what I presented:&lt;/p></description><content:encoded>
<![CDATA[<p>Back to <a href="https://gr8conf.eu/">GR8Conf Europe</a> in Denmark, for the yearly <a href="http://www.groovy-lang.org/">Groovy</a> community reunion! I had the chance to present two talks.</p>
<p>The first one on Google&rsquo;s <a href="https://cloud.google.com/products/machine-learning/">Machine Learning APIs</a>, with samples in Groovy using vision recognition, speech recognition &amp; generation, natural language analysis. I&rsquo;ll come back on ML in Groovy in forthcoming articles.</p>
<p>And the second talk was an overview of Google Cloud Platform, focusing on the compute and storage options, with demos using Groovy frameworks (<a href="https://ratpack.io/">Ratpack</a>, <a href="http://gaelyk.appspot.com/">Gaelyk</a>, and the newly released <a href="http://micronaut.io/">Micronaut</a>) and how to deploy apps on Compute Engine, Kubernetes Engine, App Engine. I&rsquo;ll also come back in further articles on those demos, but in the meantime, I wanted to share my slide deck with you all! Without further ado, here&rsquo;s what I presented:</p>
<script async class="speakerdeck-embed" data-id="a9b1167d2ffb418082553a879f51c869" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<blockquote>
<p>Time to release your marvelous project or website, powered with GR8 tech, into the wild?
Let&rsquo;s see together what Google Cloud Platform has to provide for the Apache Groovy ecosystem developer.
If you have a Grails or Ratpack app, what are the best compute options for deploying it?
What other services are useful to your app: databases with Cloud SQL, messaging
with Cloud Pub/Sub, monitoring, and more, are at your disposal.
We&rsquo;ll explore together what options you have to deploy and scale your next great idea in the Google cloud.</p></blockquote>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Ten years of App Engine with a Groovy twist</title><link>https://glaforge.dev/posts/2018/04/16/ten-years-of-app-engine-with-a-groovy-twist/</link><pubDate>Mon, 16 Apr 2018 17:29:24 +0100</pubDate><guid>https://glaforge.dev/posts/2018/04/16/ten-years-of-app-engine-with-a-groovy-twist/</guid><description>&lt;p>The venerable Google &lt;a href="https://cloud.google.com/appengine/">App Engine&lt;/a> platform celebrated its &lt;a href="https://cloudplatform.googleblog.com/2018/04/reflecting-on-our-ten-year-App-Engine-journey.html">10th anniversary&lt;/a>!&lt;/p>
&lt;p>Back in 2008, it started with Python, as its first runtime, but I got way more interested in App Engine when the Java runtime would launch the following year. It&amp;rsquo;s a bit of a special story for me, as I&amp;rsquo;ve always been a fan of App Engine, since the beginning.&lt;/p>
&lt;p>Over the years, I&amp;rsquo;ve built several apps running on App Engine. For instance, this blog you&amp;rsquo;re reading now is running on App Engine, as well as my personal picture / video sharing app, some Github post-commit webhook for the &lt;a href="http://www.groovy-lang.org/">Apache Groovy&lt;/a> project, or the &lt;a href="http://groovyconsole.appspot.com/">Groovy Web Console&lt;/a> to share / edit / run Groovy scripts in the cloud.&lt;/p></description><content:encoded>
<![CDATA[<p>The venerable Google <a href="https://cloud.google.com/appengine/">App Engine</a> platform celebrated its <a href="https://cloudplatform.googleblog.com/2018/04/reflecting-on-our-ten-year-App-Engine-journey.html">10th anniversary</a>!</p>
<p>Back in 2008, it started with Python, as its first runtime, but I got way more interested in App Engine when the Java runtime would launch the following year. It&rsquo;s a bit of a special story for me, as I&rsquo;ve always been a fan of App Engine, since the beginning.</p>
<p>Over the years, I&rsquo;ve built several apps running on App Engine. For instance, this blog you&rsquo;re reading now is running on App Engine, as well as my personal picture / video sharing app, some Github post-commit webhook for the <a href="http://www.groovy-lang.org/">Apache Groovy</a> project, or the <a href="http://groovyconsole.appspot.com/">Groovy Web Console</a> to share / edit / run Groovy scripts in the cloud.</p>
<p>App Engine is my go-to platform for deploying and running my ideas in the cloud!</p>
<p>I like to focus on the idea I want to implement, rather than thinking upfront about infrastructure, provisioning, ops, etc. App Engine was the pioneer in PaaS (Platform-as-a-Service) and the new trendy <a href="https://cloud.google.com/serverless/">Serverless</a> approach.</p>
<p>Although I&rsquo;ve ranted back in the day about the pricing changes (<a href="https://glaforge.dev/posts/2011/09/01/google-app-engine-s-new-pricing-model/">once</a> and <a href="https://glaforge.dev/posts/2011/11/25/coming-back-to-the-new-google-app-engine-pricing-policy/">twice</a>), it lead me to optimize my own apps and code. But ultimately, most of my apps run within the <a href="https://cloud.google.com/free/">free tier</a> of App Engine. The &ldquo;pay-as-you-go&rdquo; approach is appealing: for my apps, it&rsquo;s been pretty much free for my use, except on those few occasions where I had big peaks of traffic and users, and then, i only had to spend a few dollars to cope with the load, but I didn&rsquo;t even have to think about dealing with infrastructure, as App Engine was transparently scaling my apps itself, without any intervention on my part.</p>
<p><figure>
  <a href="#img-5b1f01e663ce9247dd0108dab075ba1d">
    <img src="/img/misc/google-app-engine-groovy.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-5b1f01e663ce9247dd0108dab075ba1d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/google-app-engine-groovy.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>But let&rsquo;s step back a little and let me tell you more about my story with App Engine. In 2009, thanks to my friend Dick Wall, I was contacted by Google, signed an NDA, and worked with the engineering team who was responsible for the upcoming Java runtime. As the engineering team was working on its launch, they wanted to ensure that alternative languages like <a href="http://www.groovy-lang.org/">Apache Groovy</a> would run well on the platform. So we worked hand in hand, patching Groovy to be more compliant with App Engine&rsquo;s sandboxing mechanism (which is now lifted, as past limitations are now gone in the newer runtimes.)</p>
<p>Thanks to this work on the Groovy and App Engine integration, I got the chance to present at Google I/O 2009 about running Groovy and Grails on App Engine!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/NEnniZTdOYk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And as I worked on the integration, I quickly found nice handy shortcuts thanks to the flexible nature of Groovy, and I arranged those shortcuts into a reusable library: the <a href="http://gaelyk.appspot.com/">Gaelyk</a> framework.</p>
<p>Max Ross, Toby Reyelts, Don Scwhartz, Dick Wall, Patrick Chanezon, Christian Schalk, and later on, Ludovic Champenois, Éamonn McManus, Roberto Chinnici, and many others, I&rsquo;d like to say thank you, congratulations, and happy anniversary for this lovely platform!</p>
<p>It&rsquo;s an honor for me today to <a href="https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/">work for Google Cloud Platform</a> (almost 2 years already!), and to use the awesome serverless products available, and I&rsquo;m looking forward to covering the <a href="https://cloud.google.com/serverless/">serverless</a> area even more!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>What can we learn from millions of (groovy) source files in Github</title><link>https://glaforge.dev/talks/2018/03/23/what-do-we-learn-from-millions-of-source-files-in-github/</link><pubDate>Fri, 23 Mar 2018 15:32:04 +0100</pubDate><guid>https://glaforge.dev/talks/2018/03/23/what-do-we-learn-from-millions-of-source-files-in-github/</guid><description>&lt;p>What can you learn from millions of (Groovy) source files stored on Github?
In this presentation, I analized source files in the Github archives stored on BigQuery,
and in particular Groovy source file, but also Gradle build files, or Grails controllers and services.&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="c9876f9890d84d378b5b18c9b57ad7aa" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>What kind of questions can we answer&lt;/p>
&lt;ul>
&lt;li>How many Groovy files are there on Github?&lt;/li>
&lt;li>What are the most popular Groovy file names?&lt;/li>
&lt;li>How many lines of Groovy source code are there?&lt;/li>
&lt;li>What&amp;rsquo;s the distribution of size of source files?&lt;/li>
&lt;li>What are the most frequent imported packages?&lt;/li>
&lt;li>What are the most popular Groovy APIs used?&lt;/li>
&lt;li>What are the most used AST transformations?&lt;/li>
&lt;li>Do people use import aliases much?&lt;/li>
&lt;li>Did developers adopt traits?&lt;/li>
&lt;/ul>
&lt;p>For &lt;a href="https://gradle.org/">Gradle&lt;/a>, here are the questions that I answered:&lt;/p></description><content:encoded>
<![CDATA[<p>What can you learn from millions of (Groovy) source files stored on Github?
In this presentation, I analized source files in the Github archives stored on BigQuery,
and in particular Groovy source file, but also Gradle build files, or Grails controllers and services.</p>
<script async class="speakerdeck-embed" data-id="c9876f9890d84d378b5b18c9b57ad7aa" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>What kind of questions can we answer</p>
<ul>
<li>How many Groovy files are there on Github?</li>
<li>What are the most popular Groovy file names?</li>
<li>How many lines of Groovy source code are there?</li>
<li>What&rsquo;s the distribution of size of source files?</li>
<li>What are the most frequent imported packages?</li>
<li>What are the most popular Groovy APIs used?</li>
<li>What are the most used AST transformations?</li>
<li>Do people use import aliases much?</li>
<li>Did developers adopt traits?</li>
</ul>
<p>For <a href="https://gradle.org/">Gradle</a>, here are the questions that I answered:</p>
<ul>
<li>How many Gradle build files are there?</li>
<li>How many Maven build files are there?</li>
<li>Which versions of Gradle are being used?</li>
<li>How many of those Gradle files are settings files?</li>
<li>What are the most frequent build file names?</li>
<li>What are the most frequent Gradle plugins?</li>
<li>What are the most frequent &ldquo;compile&rdquo; and &ldquo;test&rdquo; dependencies?</li>
</ul>
<p>And for <a href="https://grails.org/">Grails</a>, here&rsquo;s what I covered:</p>
<ul>
<li>What are the most used SQL database used?</li>
<li>What are the most frequent controller names?</li>
<li>What are the repositories with the biggest number of controllers?</li>
<li>What is the distribution of number of controllers?</li>
</ul>
<p>You can see a version of this talk in French in the following YouTube video, recorded at the BreizhCamp conference:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/wk2CRBRrki8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And in English at Devoxx US:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Aw4sgZ8kIeg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Keynote: IT holy wars</title><link>https://glaforge.dev/talks/2018/03/12/keynote-it-holy-wars/</link><pubDate>Mon, 12 Mar 2018 15:36:40 +0100</pubDate><guid>https://glaforge.dev/talks/2018/03/12/keynote-it-holy-wars/</guid><description>&lt;p>A few months ago, I had some fun doing the keynote of Voxxed Days Singapore and JUG Summer Camp, and I realized I didn&amp;rsquo;t get a chance to share my deck. Furthermore, the videos of both events are actually available online: one in English, one in French!&lt;/p>
&lt;p>I spoke about the so-called IT Holy Wars, you know, like Vi vs Emacs, Tabs &amp;amp; Spaces, or other funny things of that kind. How developers circle from client-side to server-side, how we are polarized across strong positions on certain frameworks or practices, on clean vs ugly code, on tooling (build, front, back), dark / light background themes for your IDE, how do you format dates, and more.&lt;/p></description><content:encoded>
<![CDATA[<p>A few months ago, I had some fun doing the keynote of Voxxed Days Singapore and JUG Summer Camp, and I realized I didn&rsquo;t get a chance to share my deck. Furthermore, the videos of both events are actually available online: one in English, one in French!</p>
<p>I spoke about the so-called IT Holy Wars, you know, like Vi vs Emacs, Tabs &amp; Spaces, or other funny things of that kind. How developers circle from client-side to server-side, how we are polarized across strong positions on certain frameworks or practices, on clean vs ugly code, on tooling (build, front, back), dark / light background themes for your IDE, how do you format dates, and more.</p>
<p>I had lots of fun preparing and delivering this talk, both in English and French.</p>
<p>Let&rsquo;s start with English deck &amp; video:</p>
<script async class="speakerdeck-embed" data-id="d3cac7c15adb45e8bf10233754b04de9" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/4MDTBBEEyho?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And now &ldquo;en français dans le texte&rdquo; : </p>
<script async class="speakerdeck-embed" data-id="f15e5f8b54544fd29408ca72dfd29885" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Build apps for voice, chat, web and mobile</title><link>https://glaforge.dev/talks/2018/02/15/build-apps-for-voice-chat-web-and-mobile/</link><pubDate>Thu, 15 Feb 2018 15:48:57 +0100</pubDate><guid>https://glaforge.dev/talks/2018/02/15/build-apps-for-voice-chat-web-and-mobile/</guid><description>&lt;p>&lt;strong>ServerlessConf Paris&lt;/strong> is in full swing this week, and I had the chance yesterday to participate in a workshop with my colleague Frank, to cover building apps for voice, chat, web and mobile, using Google&amp;rsquo;s serverless &lt;a href="https://developers.google.com/actions/">solutions. In particular, for voice &amp;amp; chat, I spoke about Dialogflow and Google Assistant / Actions&lt;/a> on Google, using &lt;a href="https://cloud.google.com/functions/">Cloud Functions&lt;/a> for my business logic, while in the afternoon we covered &lt;a href="https://firebase.google.com/">Firebase&lt;/a> un more depth.&lt;/p>
&lt;p>If you want to have a quick look at the deck, here are the slides that I presented:&lt;/p></description><content:encoded>
<![CDATA[<p><strong>ServerlessConf Paris</strong> is in full swing this week, and I had the chance yesterday to participate in a workshop with my colleague Frank, to cover building apps for voice, chat, web and mobile, using Google&rsquo;s serverless <a href="https://developers.google.com/actions/">solutions. In particular, for voice &amp; chat, I spoke about Dialogflow and Google Assistant / Actions</a> on Google, using <a href="https://cloud.google.com/functions/">Cloud Functions</a> for my business logic, while in the afternoon we covered <a href="https://firebase.google.com/">Firebase</a> un more depth.</p>
<p>If you want to have a quick look at the deck, here are the slides that I presented:</p>
<script async class="speakerdeck-embed" data-id="14c2dc2fe2d44413be0b4697469e4311" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>I'm a Java Champion!</title><link>https://glaforge.dev/posts/2018/01/18/i-m-a-java-champion/</link><pubDate>Thu, 18 Jan 2018 16:10:21 +0100</pubDate><guid>https://glaforge.dev/posts/2018/01/18/i-m-a-java-champion/</guid><description>&lt;p>The Java Champions recently nominated me to become a Java Champion!&lt;/p>
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">Please welcome the new Java Champions. &lt;a href="https://twitter.com/antoine_sd?ref_src=twsrc%5Etfw">@antoine_sd&lt;/a> &lt;a href="https://twitter.com/javajudd?ref_src=twsrc%5Etfw">@javajudd&lt;/a> &lt;a href="https://twitter.com/glaforge?ref_src=twsrc%5Etfw">@glaforge&lt;/a> &lt;a href="https://twitter.com/kenkousen?ref_src=twsrc%5Etfw">@kenkousen&lt;/a> &lt;a href="https://twitter.com/kito99?ref_src=twsrc%5Etfw">@kito99&lt;/a> &lt;a href="https://twitter.com/vlad_mihalcea?ref_src=twsrc%5Etfw">@vlad_mihalcea&lt;/a>&lt;br>@leomrlima &lt;a href="https://twitter.com/net0pyr?ref_src=twsrc%5Etfw">@net0pyr&lt;/a> &lt;a href="https://twitter.com/shelajev?ref_src=twsrc%5Etfw">@shelajev&lt;/a> @rgransberger &lt;a href="https://twitter.com/rmehmandarov?ref_src=twsrc%5Etfw">@rmehmandarov&lt;/a>  &lt;a href="https://twitter.com/Sander_Mak?ref_src=twsrc%5Etfw">@Sander_Mak&lt;/a> &lt;a href="https://twitter.com/SeanMiPhillips?ref_src=twsrc%5Etfw">@SeanMiPhillips&lt;/a> Well done to all of you &lt;a href="https://twitter.com/OracleDevs?ref_src=twsrc%5Etfw">@OracleDevs&lt;/a> &lt;a href="https://twitter.com/java?ref_src=twsrc%5Etfw">@java&lt;/a> &lt;a href="https://twitter.com/hashtag/odevcommunity?src=hash&amp;amp;ref_src=twsrc%5Etfw">#odevcommunity&lt;/a> &lt;a href="https://t.co/X9yeek641s">pic.twitter.com/X9yeek641s&lt;/a>&lt;/p>&amp;mdash; Java Champions (@Java_Champions) &lt;a href="https://twitter.com/Java_Champions/status/933194279412891648?ref_src=twsrc%5Etfw">November 22, 2017&lt;/a>&lt;/blockquote>
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8">&lt;/script>
&lt;p>There&amp;rsquo;s also a &lt;a href="https://blogs.oracle.com/java/new-java-champions-in-2017">post&lt;/a> on the Java Champions&amp;rsquo; blog, and &lt;a href="https://www.infoq.com/news/2018/01/JavaChampions2017">InfoQ&lt;/a> also echoed the new nominees recently.&lt;/p>
&lt;p>I&amp;rsquo;m super happy and proud of this nomination, and I&amp;rsquo;m looking forward to continuing being involved in the Java ecosystem, present at Java-friendly conferences, contribute to Open Source projects using Java and Apache Groovy, and write articles here and there using my favorite languages.&lt;/p></description><content:encoded>
<![CDATA[<p>The Java Champions recently nominated me to become a Java Champion!</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Please welcome the new Java Champions. <a href="https://twitter.com/antoine_sd?ref_src=twsrc%5Etfw">@antoine_sd</a> <a href="https://twitter.com/javajudd?ref_src=twsrc%5Etfw">@javajudd</a> <a href="https://twitter.com/glaforge?ref_src=twsrc%5Etfw">@glaforge</a> <a href="https://twitter.com/kenkousen?ref_src=twsrc%5Etfw">@kenkousen</a> <a href="https://twitter.com/kito99?ref_src=twsrc%5Etfw">@kito99</a> <a href="https://twitter.com/vlad_mihalcea?ref_src=twsrc%5Etfw">@vlad_mihalcea</a><br>@leomrlima <a href="https://twitter.com/net0pyr?ref_src=twsrc%5Etfw">@net0pyr</a> <a href="https://twitter.com/shelajev?ref_src=twsrc%5Etfw">@shelajev</a> @rgransberger <a href="https://twitter.com/rmehmandarov?ref_src=twsrc%5Etfw">@rmehmandarov</a>  <a href="https://twitter.com/Sander_Mak?ref_src=twsrc%5Etfw">@Sander_Mak</a> <a href="https://twitter.com/SeanMiPhillips?ref_src=twsrc%5Etfw">@SeanMiPhillips</a> Well done to all of you <a href="https://twitter.com/OracleDevs?ref_src=twsrc%5Etfw">@OracleDevs</a> <a href="https://twitter.com/java?ref_src=twsrc%5Etfw">@java</a> <a href="https://twitter.com/hashtag/odevcommunity?src=hash&amp;ref_src=twsrc%5Etfw">#odevcommunity</a> <a href="https://t.co/X9yeek641s">pic.twitter.com/X9yeek641s</a></p>&mdash; Java Champions (@Java_Champions) <a href="https://twitter.com/Java_Champions/status/933194279412891648?ref_src=twsrc%5Etfw">November 22, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>There&rsquo;s also a <a href="https://blogs.oracle.com/java/new-java-champions-in-2017">post</a> on the Java Champions&rsquo; blog, and <a href="https://www.infoq.com/news/2018/01/JavaChampions2017">InfoQ</a> also echoed the new nominees recently.</p>
<p>I&rsquo;m super happy and proud of this nomination, and I&rsquo;m looking forward to continuing being involved in the Java ecosystem, present at Java-friendly conferences, contribute to Open Source projects using Java and Apache Groovy, and write articles here and there using my favorite languages.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Pre-trained machine learning APIs</title><link>https://glaforge.dev/talks/2018/01/17/pre-trained-machine-learning-apis/</link><pubDate>Wed, 17 Jan 2018 16:15:40 +0100</pubDate><guid>https://glaforge.dev/talks/2018/01/17/pre-trained-machine-learning-apis/</guid><description>&lt;p>Last month, for the first time, I visited Riga (Latvia), for the &lt;a href="https://devternity.com/">DevTernity conference&lt;/a>. I really enjoyed my time there, and wish to come back with other topics next time. The organizers took very well care of the speakers, and the presentations were very interesting.&lt;/p>
&lt;p>I had the pleasure to talk about the &lt;a href="https://cloud.google.com/products/machine-learning/">pre-trained machine learning APIs&lt;/a> provided by Google Cloud Platform, and say a few words as well about &lt;a href="https://www.tensorflow.org/">TensorFlow&lt;/a> and &lt;a href="https://cloud.google.com/ml-engine/">Cloud ML Engine&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>Last month, for the first time, I visited Riga (Latvia), for the <a href="https://devternity.com/">DevTernity conference</a>. I really enjoyed my time there, and wish to come back with other topics next time. The organizers took very well care of the speakers, and the presentations were very interesting.</p>
<p>I had the pleasure to talk about the <a href="https://cloud.google.com/products/machine-learning/">pre-trained machine learning APIs</a> provided by Google Cloud Platform, and say a few words as well about <a href="https://www.tensorflow.org/">TensorFlow</a> and <a href="https://cloud.google.com/ml-engine/">Cloud ML Engine</a>.</p>
<p>The talk was recorded, and I wanted to share with you the video and the slides of the presentation.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/_5SmyANkq3o?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And here is the deck:</p>
<script async class="speakerdeck-embed" data-id="c2927b99b7b64b9c83a90fe81704a9f2" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The 2018 countdown: a tip a day about Google Cloud Platform</title><link>https://glaforge.dev/posts/2017/12/18/the-2018-countdown-a-tip-a-day-about-google-cloud-platform/</link><pubDate>Mon, 18 Dec 2017 16:22:19 +0100</pubDate><guid>https://glaforge.dev/posts/2017/12/18/the-2018-countdown-a-tip-a-day-about-google-cloud-platform/</guid><description>&lt;p>A few weeks ago, I&amp;rsquo;ve started a new blog dedicated to &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>, to share tips&amp;rsquo;n tricks I come across while exploring the platform, getting to know new products, or gathered through experience with a particular service I&amp;rsquo;ve been using:&lt;/p>
&lt;p>&lt;a href="https://googlecloud.tips/">https://googlecloud.tips/&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://googlecloud.tips/">&lt;figure>
&lt;a href="#img-4ea7433448ab0864c97c21021fae77ff">
&lt;img src="https://glaforge.dev/img/gcp-tips/gcp-tips-frontpage.jpg"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-4ea7433448ab0864c97c21021fae77ff">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/gcp-tips/gcp-tips-frontpage.jpg"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/a>&lt;/p>
&lt;p>With the holidays season, I went with a &amp;ldquo;2018 countdown&amp;rdquo; approach (like an &amp;ldquo;advent calendar&amp;rdquo; without the religious connotation), where I publish a tip every day of the month of December.&lt;/p></description><content:encoded>
<![CDATA[<p>A few weeks ago, I&rsquo;ve started a new blog dedicated to <a href="https://cloud.google.com/">Google Cloud Platform</a>, to share tips&rsquo;n tricks I come across while exploring the platform, getting to know new products, or gathered through experience with a particular service I&rsquo;ve been using:</p>
<p><a href="https://googlecloud.tips/">https://googlecloud.tips/</a></p>
<p><a href="https://googlecloud.tips/"><figure>
  <a href="#img-4ea7433448ab0864c97c21021fae77ff">
    <img src="/img/gcp-tips/gcp-tips-frontpage.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-4ea7433448ab0864c97c21021fae77ff">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gcp-tips/gcp-tips-frontpage.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<p>With the holidays season, I went with a &ldquo;2018 countdown&rdquo; approach (like an &ldquo;advent calendar&rdquo; without the religious connotation), where I publish a tip every day of the month of December.</p>
<p>As of today, December 18th, we already have 18 tips available!</p>
<p>Those tips span about a dozen technologies! (as you can also explore the tips via &ldquo;tags&rdquo;, which represent a technology / service / API / product each)</p>
<p><figure>
  <a href="#img-a187ac9487c184c6aacf737a025e3d5f">
    <img src="/img/gcp-tips/gcptips-tags.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a187ac9487c184c6aacf737a025e3d5f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gcp-tips/gcptips-tags.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Initially, I thought it would be a challenge to author tips every day (without even thinking of the fact you have to publish tips during weekends, holidays or vacations), but there are actually plenty of tricks to share. Furthermore, I opened the blog to contributions: anyone who wants to contribute a tip or two is welcome, and should just share with me a quick gist describing the tip. I&rsquo;ve already received a bunch of contributions, from 8 distinct authors. Thanks a lot to Alexandre, Bastien, Fabien, Graham, Jim, Mark, Victor, or Wassim!</p>
<p>Although it all started as a tip a day for the 2018 countdown, it won&rsquo;t stop there. Perhaps the frequency will be a bit lower (once a week? more?), but I definitely intend on continuing sharing tips on a regular basis next year and beyond!</p>
<p>If you want to help, please spread the word! Tell your friends and colleagues about the site: <a href="https://googlecloud.tips/">https://googlecloud.tips/</a>.</p>
<p>Also please follow the <a href="https://twitter.com/GcpTips">@gcptips</a> Twitter account where new tips&rsquo;n tricks are announced.</p>
<p>And if you&rsquo;ve got some time, don&rsquo;t hesitate to share your own tips! All help is welcome :-)</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Gradle vs Maven and Gradle in Kotlin or Groovy</title><link>https://glaforge.dev/posts/2017/11/27/gradle-vs-maven-and-gradle-in-kotlin-or-groovy/</link><pubDate>Mon, 27 Nov 2017 16:29:35 +0100</pubDate><guid>https://glaforge.dev/posts/2017/11/27/gradle-vs-maven-and-gradle-in-kotlin-or-groovy/</guid><description>&lt;p>Once in a while, when talking about Gradle with developers, at conferences or within the Groovy community (but with the wider Java community as well), I hear questions about Gradle. In particular Gradle vs Maven, or whether developers adopt the Kotlin DSL for Gradle builds.&lt;/p>
&lt;p>In the past, I blogged several times about using BigQuery and the Github dataset to analyze open source projects hosted on Github, by running some SQL queries against that dataset. You might want to have a look at this past article on some &lt;a href="https://glaforge.dev/posts/2016/12/03/analyzing-half-a-million-gradle-build-files/">Gradle analysis with BigQuery&lt;/a>. Considering those questions popped up recently, I decided to do a quick run through those questions with some simple queries.&lt;/p></description><content:encoded>
<![CDATA[<p>Once in a while, when talking about Gradle with developers, at conferences or within the Groovy community (but with the wider Java community as well), I hear questions about Gradle. In particular Gradle vs Maven, or whether developers adopt the Kotlin DSL for Gradle builds.</p>
<p>In the past, I blogged several times about using BigQuery and the Github dataset to analyze open source projects hosted on Github, by running some SQL queries against that dataset. You might want to have a look at this past article on some <a href="https://glaforge.dev/posts/2016/12/03/analyzing-half-a-million-gradle-build-files/">Gradle analysis with BigQuery</a>. Considering those questions popped up recently, I decided to do a quick run through those questions with some simple queries.</p>
<h2 id="gradle-vs-maven">Gradle vs Maven?</h2>
<p>First, let&rsquo;s look at Maven builds. We can run the following query:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%pom.xml&#39;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 1,125,150 pom files.</p>
<p><figure>
  <a href="#img-43a17394b8f79d4c02230ddb6d395693">
    <img src="/img/bq-groovy/gradle-builds-vs-maven-builds.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-43a17394b8f79d4c02230ddb6d395693">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/gradle-builds-vs-maven-builds.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then, for Gradle, I ran this query (even if projects could have different build file names):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%build.gradle&#39;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 414,329 build.gradle files.</p>
<p><figure>
  <a href="#img-bc8f9c55b06ad52009b95237c8ec0c59">
    <img src="/img/bq-groovy/gradle-builds-in-groovy.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-bc8f9c55b06ad52009b95237c8ec0c59">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/gradle-builds-in-groovy.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>So that&rsquo;s 1 Gradle build file for 2.7 Maven build file.</p>
<h2 id="gradle-builds-in-kotlin-or-in-groovy">Gradle builds in Kotlin or in Groovy?</h2>
<p>Now for Kotlin, the convention seems to be about naming your build files with build.gradle.kts. So let&rsquo;s run the following query:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%build.gradle.kts&#39;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>There are only 207 Gradle builds files written in Kotlin.</p>
<p><figure>
  <a href="#img-aad7cc7344be01350828e90f9e0b1d1b">
    <img src="/img/bq-groovy/gradle-builds-in-kotlin.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-aad7cc7344be01350828e90f9e0b1d1b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/gradle-builds-in-kotlin.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Basically, Groovy-based Gradle builds are 2000 times more popular than Kotlin-based builds.</p>
<h2 id="a-grain-of-salt">A grain of salt</h2>
<p>Now, all that said, remember that developers can name their build files differently, that it&rsquo;s only a snapshot of the projects available on Github, and furthermore, just open source projects (at least projects that explicitly have a LICENSE file). Note for example as well that there are Gradle based projects that also have a pom.xml file available, although they&rsquo;re not using Maven for their build.</p>
<p>Also, perhaps it&rsquo;d be more interesting to run the queries by counting repositories, rather than build files: Perhaps Gradle users tend to split their build files in smaller build files, in a less monolithic way than with Gradle? Practices and habits may vary greatly.</p>
<p>For the Gradle vs Maven question, at Devoxx Belgium, I ran the following (more complex) query where I look at repositories containing Gradle or Maven build files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#666">#</span>standardSQL<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">select</span><span style="color:#bbb"> </span>file,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(file)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>(<span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;gradle&#39;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>file,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(repo_name)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span><span style="color:#666">`</span>bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>.github_repos.files<span style="color:#666">`</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%build.gradle&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>repo_name)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">UNION</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">ALL</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>(<span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#34;maven&#34;</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>file,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(repo_name)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span><span style="color:#666">`</span>bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>.github_repos.files<span style="color:#666">`</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%pom.xml&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>repo_name)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">group</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">by</span><span style="color:#bbb"> </span>file<span style="color:#bbb">
</span></span></span></code></pre></div><p>Gradle and Maven are already much closer to each other by looking at repository counts than just by pure number of build files, perhaps indeed showing a trend with Gradle users to modularize their builds more.</p>
<p>We get 118,386 repositories using Gradle versus 143,290 repositories using Maven. So Gradle is almost at the same level as Maven from that repository perspective, Still catching up with Maven!</p>
<h2 id="famous-last-words">Famous last words</h2>
<p>Don&rsquo;t necessarily draw too big conclusions out of those figures, there are many ways to make stats, and those figures are only a small fraction of all the projects in existence in the world&hellip; but at least, they certainly exhibit a certain trend, which is still interesting to know and think about!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The JDK built-in web server with Apache Groovy</title><link>https://glaforge.dev/posts/2017/11/20/the-jdk-builtin-web-server-with-apache-groovy/</link><pubDate>Mon, 20 Nov 2017 16:38:29 +0100</pubDate><guid>https://glaforge.dev/posts/2017/11/20/the-jdk-builtin-web-server-with-apache-groovy/</guid><description>&lt;p>In my timeline, I saw a tweet from Joe Walnes about the built-in HTTP server available in the JDK since Java 6. It&amp;rsquo;s super convenient, starts super fast, easy to use, but I often forget about it. I&amp;rsquo;d probably not use it for serving planet-wide load, but it&amp;rsquo;s very useful when you need to create a quick service, a little mock for testing some web or micro-service.&lt;/p>
&lt;p>Here&amp;rsquo;s a little hello world for the fun.&lt;/p></description><content:encoded>
<![CDATA[<p>In my timeline, I saw a tweet from Joe Walnes about the built-in HTTP server available in the JDK since Java 6. It&rsquo;s super convenient, starts super fast, easy to use, but I often forget about it. I&rsquo;d probably not use it for serving planet-wide load, but it&rsquo;s very useful when you need to create a quick service, a little mock for testing some web or micro-service.</p>
<p>Here&rsquo;s a little hello world for the fun.</p>
<p>I&rsquo;m taking advantage of Apache Groovy&rsquo;s closure-to-functional-interface coercion support, as well as the <code>with{}</code> method to reuse the <code>HttpServer</code> instance for two method calls on the same instance (I could&rsquo;ve used it for the <code>http</code> variable as well, actually).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.sun.net.httpserver.HttpServer</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HttpServer<span style="color:#666">.</span><span style="color:#4070a0">create</span><span style="color:#666">(</span><span style="color:#007020;font-weight:bold">new</span> InetSocketAddress<span style="color:#666">(</span><span style="color:#40a070">8080</span><span style="color:#666">),</span> <span style="color:#40a070">0</span><span style="color:#666">).</span><span style="color:#4070a0">with</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    createContext<span style="color:#666">(</span><span style="color:#4070a0">&#34;/hello&#34;</span><span style="color:#666">)</span> <span style="color:#666">{</span> http <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>        http<span style="color:#666">.</span><span style="color:#4070a0">responseHeaders</span><span style="color:#666">.</span><span style="color:#4070a0">add</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;Content-type&#34;</span><span style="color:#666">,</span> <span style="color:#4070a0">&#34;text/plain&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        http<span style="color:#666">.</span><span style="color:#4070a0">sendResponseHeaders</span><span style="color:#666">(</span><span style="color:#40a070">200</span><span style="color:#666">,</span> <span style="color:#40a070">0</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        http<span style="color:#666">.</span><span style="color:#4070a0">responseBody</span><span style="color:#666">.</span><span style="color:#4070a0">withWriter</span> <span style="color:#666">{</span> out <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>            out <span style="color:#666">&lt;&lt;</span> <span style="color:#4070a0">&#34;Hello ${http.remoteAddress.hostName}!&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    start<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>More voice control for Actions on Google</title><link>https://glaforge.dev/posts/2017/11/16/more-voice-control-for-actions-on-google/</link><pubDate>Thu, 16 Nov 2017 16:40:32 +0100</pubDate><guid>https://glaforge.dev/posts/2017/11/16/more-voice-control-for-actions-on-google/</guid><description>&lt;p>Today, there were some interesting &lt;a href="https://developers.googleblog.com/2017/11/help-users-find-interact-re-engage-with.html">announcements for Actions on Google&lt;/a>, for building your conversational interfaces for the &lt;a href="https://assistant.google.com/">Google Assistant&lt;/a>. Among the great news, one item particularly caught my attention: the improved SSML support:&lt;/p>
&lt;link rel="stylesheet" href="https://glaforge.dev/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&amp;#43;AD&amp;#43;9EeYXUTm&amp;#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
&lt;div class="admonition info">
&lt;div class="admonition-header">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">&lt;path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>&lt;/svg>
&lt;span>Better SSML&lt;/span>
&lt;/div>
&lt;div class="admonition-content">
&lt;p>We recently rolled out an update to the web simulator
which includes a new SSML audio design experience.
We now give you more options for creating natural,
quality dialog using newly supported SSML tags, including &amp;lt;prosody&amp;gt;,
&amp;lt;emphasis&amp;gt;, &amp;lt;audio&amp;gt; and others. The new tag &amp;lt;par&amp;gt; is coming soon
and lets you add mood and richness, so you can play background music
and ambient sounds while a user is having a conversation with your app.
To help you get started, we&amp;rsquo;ve added over 1,000 sounds to the sound library.
Listen to a brief SSML audio experiment that shows off some of the new features here.&lt;/p></description><content:encoded>
<![CDATA[<p>Today, there were some interesting <a href="https://developers.googleblog.com/2017/11/help-users-find-interact-re-engage-with.html">announcements for Actions on Google</a>, for building your conversational interfaces for the <a href="https://assistant.google.com/">Google Assistant</a>. Among the great news, one item particularly caught my attention: the improved SSML support:</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Better SSML</span>
      </div>
      <div class="admonition-content">
        <p>We recently rolled out an update to the web simulator
which includes a new SSML audio design experience.
We now give you more options for creating natural,
quality dialog using newly supported SSML tags, including &lt;prosody&gt;,
&lt;emphasis&gt;, &lt;audio&gt; and others. The new tag &lt;par&gt; is coming soon
and lets you add mood and richness, so you can play background music
and ambient sounds while a user is having a conversation with your app.
To help you get started, we&rsquo;ve added over 1,000 sounds to the sound library.
Listen to a brief SSML audio experiment that shows off some of the new features here.</p>
      </div>
    </div><p>SSML stands for <a href="https://www.w3.org/TR/speech-synthesis/">Speech Synthesis Markup Language</a>. It&rsquo;s a W3C standard whose goal is to provide better support for a more natural sounding speech generation.</p>
<p>So far, <a href="https://developers.google.com/actions/">Actions on Google</a> had limited SSML support, but today, there&rsquo;s a bit more you can do with SSML to enhance your apps&rsquo; voice!</p>
<p>At the Devoxx Belgium conference last week, in a couple of talks showing Dialogflow, </p>
<p>Actions on Google, and Cloud Functions, I showed some quick examples of SSML.</p>
<p>For example, I <a href="https://youtu.be/7NjRqMYH11s?t=40m41s">made an attendee do some squats on stage</a>! (but the camera didn&rsquo;t catch that unfortunately.) I created a loop over a tick-tock sound to mimick a countdown. I repeated x times the tick-tock sound. With x audio elements. But we can do better now, by using the repeatCount attribute instead!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;audio</span> <span style="color:#4070a0">src=</span><span style="color:#4070a0">&#34;gs://my-bucket-sounds/tick-tock-1s.wav&#34;</span> <span style="color:#4070a0">repeatCount=</span><span style="color:#4070a0">&#34;10&#34;</span> <span style="color:#062873;font-weight:bold">/&gt;</span>
</span></span></code></pre></div><p>It&rsquo;s much better than repeating my audio tag 10 times!</p>
<p>If you want to make your interactions even more lively, you could already use the Actions on Google <a href="https://developers.google.com/actions/tools/sound-library/">sound library</a>, or use a free sound library like <a href="https://www.freesound.org/">Freesound</a>.</p>
<p>But there&rsquo;s a promising upcoming tag that&rsquo;s gonna be supported soon: &lt;par/&gt;</p>
<p>If you will, par is a bit like a multi-track audio mixer. You&rsquo;ll be able to play different sounds in parallel, or make the voice speak in parallel. So you could very well have a background sound or music, with your app speaking at the same time.</p>
<p>Speaking of voice, the human voice goes up and down in pitch. With the prosody element, you can define the rate, pitch, and volume attributes. For instance, I make my voice sing some notes with semitones (but to be honest, it doesn&rsquo;t quite sound yet like a real singer!)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;speak&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;-7st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>C<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;-5st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>D<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;-3st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>E<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;-2st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>F<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;0st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>G<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;+2st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>A<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;+4st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>B<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;prosody</span> <span style="color:#4070a0">rate=</span><span style="color:#4070a0">&#34;slow&#34;</span> <span style="color:#4070a0">pitch=</span><span style="color:#4070a0">&#34;+6st&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>C<span style="color:#062873;font-weight:bold">&lt;/prosody&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/speak&gt;</span>
</span></span></code></pre></div><p>You can also play with different levels of emphasis:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;speak&gt;</span>
</span></span><span style="display:flex;"><span>  This is 
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&lt;emphasis</span> <span style="color:#4070a0">level=</span><span style="color:#4070a0">&#34;strong&#34;</span><span style="color:#062873;font-weight:bold">&gt;</span>really<span style="color:#062873;font-weight:bold">&lt;/emphasis&gt;</span>
</span></span><span style="display:flex;"><span>  cool!
</span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">&lt;/speak&gt;</span>
</span></span></code></pre></div><p>Learn more about all the <a href="https://developers.google.com/actions/reference/ssml">support SSML tags in the Actions on Google documentation</a>! It&rsquo;s gonna be even more fun to create lively voice interactions with all those improvements!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>JavaOne — How languages influence each other: Reflections on 14 years of Apache Groovy</title><link>https://glaforge.dev/talks/2017/10/09/javaone-how-languages-influence-each-other-reflections-on-14-years-of-apache-groovy/</link><pubDate>Mon, 09 Oct 2017 16:42:54 +0100</pubDate><guid>https://glaforge.dev/talks/2017/10/09/javaone-how-languages-influence-each-other-reflections-on-14-years-of-apache-groovy/</guid><description>&lt;p>Last week, I was in San Francisco for my tenth JavaOne! I had two sessions: one on the past / present / future of Java Platform-as-a-Service offerings, and one on programming language influences, and particularly how was Apache Groovy influenced, and how it also inspired other languages.&lt;/p>
&lt;p>Here&amp;rsquo;s the abstract:&lt;/p>
&lt;blockquote>
&lt;p>Languages have been influencing one another since the dawn of computer programming. There are families of languages: from Algol descendants with begin/end code blocks to those with curly braces such as C. Languages are not invented in a vacuum but are inspired by their predecessors. This session&amp;rsquo;s speaker, who has been working on Apache Groovy for the past 14 years, reflects on the influences that have driven the design of programming languages. In particular, Groovy&amp;rsquo;s base syntax was directly derived from Java&amp;rsquo;s but quickly developed its own flavor, adding closures, type inference, and operators from Ruby. Groovy also inspired other languages: C#, Swift, and JavaScript adopted Groovy&amp;rsquo;s null-safe navigation operator and the famous Elvis operator.&lt;/p></description><content:encoded>
<![CDATA[<p>Last week, I was in San Francisco for my tenth JavaOne! I had two sessions: one on the past / present / future of Java Platform-as-a-Service offerings, and one on programming language influences, and particularly how was Apache Groovy influenced, and how it also inspired other languages.</p>
<p>Here&rsquo;s the abstract:</p>
<blockquote>
<p>Languages have been influencing one another since the dawn of computer programming. There are families of languages: from Algol descendants with begin/end code blocks to those with curly braces such as C. Languages are not invented in a vacuum but are inspired by their predecessors. This session&rsquo;s speaker, who has been working on Apache Groovy for the past 14 years, reflects on the influences that have driven the design of programming languages. In particular, Groovy&rsquo;s base syntax was directly derived from Java&rsquo;s but quickly developed its own flavor, adding closures, type inference, and operators from Ruby. Groovy also inspired other languages: C#, Swift, and JavaScript adopted Groovy&rsquo;s null-safe navigation operator and the famous Elvis operator.</p></blockquote>
<p>And you can have a look at the slides below:</p>
<script async class="speakerdeck-embed" data-id="2159d9f06d0b4b388a849823f43e82fc" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p><a href="http://www.groovy-lang.org/">Apache Groovy</a> is a multi-faceted language for the Java platform, allowing developers to code in a Java-friendly syntax, with great integration with the Java ecosystem, and powerful scripting and Domain-Specific Language capabilities, while at the same time being able to offer you type safety and static compilation.</p>
<p>In this presentation, I revisited some of the influences from other languages, from the C-family and its Java older brother, going through its Python-inspired strings, its Smalltalk and Ruby heritage for named parameters and closures, its type system à-la-Java. But I&rsquo;m also showing some of the innovations Groovy came up with that were later borrowed by others (Swift, C#, Kotlin, Ceylon, PHP, Ruby, Coffeescript&hellip;). Things like Groovy&rsquo;s trailing closure, Groovy builders, null-safe navigation, the Elvis operator, ranges, the spaceship operator, and more.</p>
<p>Ultimately, inspiration is really a two-way street, as languages don&rsquo;t come from nowhere and inherit from their older brothers and sisters. No language is perfect, but each one of them somehow help the next ones to get better, by borrowing here and there some nice features that make developers more productive and write more readable and maintainable code.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Building your own chatbots with API.ai and Cloud Functions</title><link>https://glaforge.dev/talks/2017/10/08/building-your-own-chatbots-with-apiai-and-cloud-functions/</link><pubDate>Sun, 08 Oct 2017 16:56:13 +0100</pubDate><guid>https://glaforge.dev/talks/2017/10/08/building-your-own-chatbots-with-apiai-and-cloud-functions/</guid><description>&lt;p>A few weeks ago, my buddy &lt;a href="https://twitter.com/manekinekko">Wassim&lt;/a> and I had the chance to present again on the topic of chatbots, with API.AI and Cloud Functions, at the &lt;a href="https://devfesttoulouse.fr/">DevFest Toulouse&lt;/a> conference.&lt;/p>
&lt;p>Here&amp;rsquo;s the latest update to our slide deck:&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="1b47c1e6bb7c4f81b5e0237cbfbda1ca" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>There&amp;rsquo;s also a video (in French) of the same content from Devoxx France, where I was showing how to build a conference chatbot:&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/vNoiTnnlGC0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>Chatbots, per se, are not really new, in the sense that we&amp;rsquo;ve been developing bots for things like IRC for a long time, but back in the day, it was simply some regular expression labor of love, rather than the natural language that we use today. The progress in machine learning, in both speech recognition (for when you use devices like Google Home) and natural language understanding (NLU), is what led us to being able to speak and chat naturally to those chatbots we encounter now.&lt;/p></description><content:encoded>
<![CDATA[<p>A few weeks ago, my buddy <a href="https://twitter.com/manekinekko">Wassim</a> and I had the chance to present again on the topic of chatbots, with API.AI and Cloud Functions, at the <a href="https://devfesttoulouse.fr/">DevFest Toulouse</a> conference.</p>
<p>Here&rsquo;s the latest update to our slide deck:</p>
<script async class="speakerdeck-embed" data-id="1b47c1e6bb7c4f81b5e0237cbfbda1ca" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>There&rsquo;s also a video (in French) of the same content from Devoxx France, where I was showing how to build a conference chatbot:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/vNoiTnnlGC0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Chatbots, per se, are not really new, in the sense that we&rsquo;ve been developing bots for things like IRC for a long time, but back in the day, it was simply some regular expression labor of love, rather than the natural language that we use today. The progress in machine learning, in both speech recognition (for when you use devices like Google Home) and natural language understanding (NLU), is what led us to being able to speak and chat naturally to those chatbots we encounter now.</p>
<p>In this presentation, we&rsquo;re covering the key concepts that underpin the NLU aspects:</p>
<ul>
<li><strong>Intents</strong> &mdash; the various kind of sentences or actions that are recognized (ex: &ldquo;I-want-to-eat-something&rdquo;)</li>
<li><strong>Entities</strong> &mdash; the concepts and values that we manipulate or that are parameterizing intents (ex: the kind of food associated with the &ldquo;I-want-to-eat-something&rdquo; intent)</li>
<li><strong>Context</strong> &mdash; a conversation is not just a request-reply exchange, but the discussion between you and the chatbot can span longer back&rsquo;n forth exchanges, and the chatbot needs to remember what was previously said to be useful and avoid any frustration for the user</li>
</ul>
<p>We&rsquo;re also clarifying some of the terminology used when working with the Google Assistant and its Actions on Google developer platform:</p>
<ul>
<li><strong>Google Assistant</strong> &mdash; a conversation between you and Google to help GTD</li>
<li><strong>Google Home</strong> &mdash; voice activated speaker powered by the Google Assistant</li>
<li><strong>Google Assistant SDK</strong> &mdash; kit to embed the Google Assistant in your devices</li>
<li><strong>Agent / chatbot / action</strong> &mdash; an actual app serving a particular purpose</li>
<li><strong>Actions on Google</strong> &mdash; developer platform to build apps for the Assistant</li>
<li><strong>Apps for the Google Assistant</strong> &mdash; 3rd party apps integrated to the Assistant</li>
<li><strong>Actions SDK</strong> &mdash; a software SDK for creating apps</li>
<li><strong>API.AI</strong> (now called <strong>DialogFlow</strong>) &mdash; a platform for creating conversational interfaces</li>
</ul>
<p>It&rsquo;s important that your chatbot has a consistent persona, that corresponds to the core values or attributes of your brand, the spirit of your bot. A bot for children will likely be more friendly and use easy to understand vocabulary, vs a more formal tone for, say, a bank chatbot).</p>
<p>There are some great resources available for seeing if your chatbot and its conversation is ready for prime time:</p>
<ul>
<li><a href="http://g.co/dev/ActionsChecklist">g.co/dev/ActionsChecklist</a> &mdash; a checklist with various aspects to double check</li>
<li><a href="http://g.co/dev/ActionsDesign">g.co/dev/ActionsDesign</a> &mdash; several useful guides explaining how proper human conversation work</li>
</ul>
<p>Our tool of choice for our demo is <a href="https://api.ai/">API.AI</a>, for implementing the voice interactions. It&rsquo;s clearly one of the best platforms on the market that makes it simple to create intents, entities, handle contexts, deal with many predefined entity types, that also provides various pre-built conversations that you can peruse.</p>
<p>For the business logic, we went with <a href="https://cloud.google.com/functions/">Google Cloud Functions</a> which allows us to define our logic using JavaScript and Node.JS. We also took advantage of the local <a href="https://cloud.google.com/functions/docs/emulator">Cloud Functions emulator</a>, to run our logic on our local machine, and <a href="https://ngrok.com/">ngrok</a> for creating a tunnel between that local machine and API.AI. In API.AI, in the fulfillment webhook, you&rsquo;ll put the temporary URL given by ngrok, that then points at your local machine, via ngrok&rsquo;s tunnel. That way, you can see changes immediately, thanks to the live reloading supported by the emulator, making it easy to evolve your code.</p>
<p>Cloud Functions is Google&rsquo;s function-as-a-service offering, which is a serverless service, taylored for event-oriented systems as well as for direct HTTP invocation, and you pay only as you go, as requests are made or events are sent to your function. It&rsquo;s a cost effective solution, that scale automatically with your load.</p>
<p>To finish, we&rsquo;re also saying a few words about how to submit your bot to the Actions on Google development platform, to extend the Google Assistant with your own ideas.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Cloud Shell and its Orion-based text editor to develop in the cloud</title><link>https://glaforge.dev/posts/2017/08/07/cloud-shell-and-its-orion-based-text-editor-to-develop-in-the-cloud/</link><pubDate>Mon, 07 Aug 2017 23:42:11 +0100</pubDate><guid>https://glaforge.dev/posts/2017/08/07/cloud-shell-and-its-orion-based-text-editor-to-develop-in-the-cloud/</guid><description>&lt;p>After deploying in the cloud, there&amp;rsquo;s a new trend towards programming in the cloud. Although I&amp;rsquo;m not sure we&amp;rsquo;re quite there yet, there are a couple of handy tools I&amp;rsquo;ve been enjoying when working on the &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>.&lt;/p>
&lt;p>I had been using the built-in &lt;a href="https://cloud.google.com/shell/">Cloud Shell&lt;/a> console, on the Google Cloud console, to have a terminal already pre-configured for my Google Cloud project. It allows you to easily have access to your whole environment, run commands, etc, just like you would from your own computer. The fact that all the command-line tools you can imagine (gradle, maven, gcloud sdk, etc) are already there is helpful, as well as the fact that you are already configured for using other cloud services.&lt;/p></description><content:encoded>
<![CDATA[<p>After deploying in the cloud, there&rsquo;s a new trend towards programming in the cloud. Although I&rsquo;m not sure we&rsquo;re quite there yet, there are a couple of handy tools I&rsquo;ve been enjoying when working on the <a href="https://cloud.google.com/">Google Cloud Platform</a>.</p>
<p>I had been using the built-in <a href="https://cloud.google.com/shell/">Cloud Shell</a> console, on the Google Cloud console, to have a terminal already pre-configured for my Google Cloud project. It allows you to easily have access to your whole environment, run commands, etc, just like you would from your own computer. The fact that all the command-line tools you can imagine (gradle, maven, gcloud sdk, etc) are already there is helpful, as well as the fact that you are already configured for using other cloud services.</p>
<p>To launch the shell, look no further than the top right hand corner, and click on the little [&gt;_] button. It will launch the terminal in the bottom part of your cloud console.</p>
<p><figure>
  <a href="#img-bcd12677814b2fffaaac0866ca0b6430">
    <img src="/img/cloud-shell/cloud-shell-launch.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-bcd12677814b2fffaaac0866ca0b6430">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/cloud-shell/cloud-shell-launch.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
 </p>
<p>You will see the console popping up below, and you&rsquo;ll be ready to access your project&rsquo;s environment:</p>
<p><figure>
  <a href="#img-63d2860ee51fef74093574c0a7c0f098">
    <img src="/img/cloud-shell/cloud-shell-terminal.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-63d2860ee51fef74093574c0a7c0f098">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/cloud-shell/cloud-shell-terminal.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>But look at this little pen icon above? If you click it, you&rsquo;ll get your terminal in full screen in another window, but more interestingly, it will launch a proper file editor! It&rsquo;s an editor based on <a href="https://orionhub.org/">Eclipse Orion&rsquo;s web editor</a>. You have your usual file browsing pane, to navigate to and select which files you want to edit, and you also have things like syntax highlighting to better understand the code at hand.</p>
<p><figure>
  <a href="#img-c9cca44ec3a38af869d8521c110af274">
    <img src="/img/cloud-shell/cloud-shell-editor.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c9cca44ec3a38af869d8521c110af274">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/cloud-shell/cloud-shell-editor.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The more friendly those built-in web editors will become, the sooner we&rsquo;ll really be able to develop in the cloud. I believe I will still continue to work on my local computer a long, but there are already times when I prefer running some operations directly in the cloud: for example, tasks that are really network hungry, they benefit directly from the wonderful network that cloud shell has access to, which is much snappier than the connection I have at home on my DSL router. For example, running some Docker build command, or fetching tons of dependencies for Node or Maven/Gradle, and it&rsquo;s really much nicer and faster within Cloud Shell. So having the added capability of also editing some files in my project make things pretty snappy.</p>
<p>There was a recent article on the Google Cloud blog outlining the <a href="https://cloudplatform.googleblog.com/2017/07/Cloud-Shells-code-editor-now-in-beta.html">beta launch of the Cloud Shell&rsquo;s code editor</a>, which is why I wanted to play with this new built-in editor.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Apache Groovy and Google App Engine at JavaOne</title><link>https://glaforge.dev/posts/2017/07/21/apache-groovy-and-google-app-engine-at-javaone/</link><pubDate>Fri, 21 Jul 2017 23:45:09 +0100</pubDate><guid>https://glaforge.dev/posts/2017/07/21/apache-groovy-and-google-app-engine-at-javaone/</guid><description>&lt;p>I&amp;rsquo;ll be back at &lt;a href="https://www.oracle.com/javaone/index.html">JavaOne&lt;/a> in San Francisco in October to speak about &lt;a href="http://www.groovy-lang.org/">Apache Groovy&lt;/a> and &lt;a href="https://cloud.google.com/appengine/">Google App Engine&lt;/a>.&lt;/p>
&lt;h2 id="apache-groovy">Apache Groovy&lt;/h2>
&lt;p>I&amp;rsquo;ve been involved with the Apache Groovy project for 14 years now, it&amp;rsquo;s a long time, and it&amp;rsquo;s interesting to see how the language has evolved over time, how it was influenced by other languages, but also how it influenced those other languages itself! Let&amp;rsquo;s see which operators or syntax constructs evolved and moved from one to the other.&lt;/p></description><content:encoded>
<![CDATA[<p>I&rsquo;ll be back at <a href="https://www.oracle.com/javaone/index.html">JavaOne</a> in San Francisco in October to speak about <a href="http://www.groovy-lang.org/">Apache Groovy</a> and <a href="https://cloud.google.com/appengine/">Google App Engine</a>.</p>
<h2 id="apache-groovy">Apache Groovy</h2>
<p>I&rsquo;ve been involved with the Apache Groovy project for 14 years now, it&rsquo;s a long time, and it&rsquo;s interesting to see how the language has evolved over time, how it was influenced by other languages, but also how it influenced those other languages itself! Let&rsquo;s see which operators or syntax constructs evolved and moved from one to the other.</p>
<h2 id="google-app-engine">Google App Engine</h2>
<p>These days, the hype is around containers, containers everywhere! We tend to relegate Platform-as-a-Service solutions to the side, but it&rsquo;s still one of the most convenient way to deploy and scale an application today. After all, Snapchat and others are able to take advantage of a PaaS like App Engine, so why couldn&rsquo;t you too? (and you don&rsquo;t need to scale to their level anyway, but you&rsquo;d still get the convenience of easy development and deployment)</p>
<p>Anyhow, I&rsquo;ve invited my friends from Heroku and Oracle to join me for a panel discussion on the theme of Java PaaS-es. We&rsquo;ll see how Java PaaS-es are relevant today, more than ever.</p>
<h2 id="the-abstracts">The abstracts</h2>
<p>So if you want to lear more about those <a href="https://events.rainfocus.com/catalog/oracle/oow17/catalogjavaone17?search=%22Guillaume%20Laforge%22&amp;showEnrolled=false">two talks</a>, here are their abstracts.</p>
<h3 id="con5034how-languages-influence-each-other-reflections-on-14-years-of-apache-groovy">[CON5034] How Languages Influence Each Other: Reflections on 14 Years of Apache Groovy</h3>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Abstract</span>
      </div>
      <div class="admonition-content">
        <p>Languages have been influencing one another since the dawn of computer programming. There are families of languages: from Algol descendants with begin/end code blocks to those with curly braces such as C. Languages are not invented in a vacuum but are inspired by their predecessors. This session&rsquo;s speaker, who has been working on Apache Groovy for the past 14 years, reflects on the influences that have driven the design of programming languages. In particular, Groovy&rsquo;s base syntax was directly derived from Java&rsquo;s but quickly developed its own flavor, adding closures, type inference, and operators from Ruby. Groovy also inspired other languages: C#, Swift, and JavaScript adopted Groovy&rsquo;s null-safe navigation operator and the famous Elvis operator.</p>
      </div>
    </div><h3 id="con5945java-paas----then-now-and-next">[CON5945] Java PaaS &ndash; Then, Now and Next</h3>

    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Abstract</span>
      </div>
      <div class="admonition-content">
        <p>Java developers want to deploy their apps easily. Fortunately, there are great solutions for them in the form of Platform-as-a-Service for Java. In this discussion panel, we will share the views of Oracle, Heroku and Google engineers about their respective Java PaaS-es, how this space has evolved over the past few years, and what makes a great developer experience for users today. We&rsquo;ll discuss the future of PaaS in light of new technologies like microservices, containerization, and serverless architectures. Finally, we&rsquo;ll open up the space for an interactive discussion with the audience.</p>
      </div>
    </div><p>(with Joe Kutner from Heroku, Shaun Smith from Oracle, Ludovic Champenois from Google, and Frank Greco from NY JavaSIG as moderator)</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Scale an OpenAPI based web API with Cloud Endpoints</title><link>https://glaforge.dev/posts/2017/07/18/scale-an-openapi-based-web-api-with-cloud-endpoints/</link><pubDate>Tue, 18 Jul 2017 23:49:14 +0100</pubDate><guid>https://glaforge.dev/posts/2017/07/18/scale-an-openapi-based-web-api-with-cloud-endpoints/</guid><description>&lt;p>InfoQ recently released a &lt;a href="https://www.infoq.com/presentations/web-api-scale-google-cloud">video&lt;/a> from the &lt;a href="http://paris.apidays.io/">APIDays&lt;/a> conference that took place in Paris last year. I talked about scaling an &lt;a href="https://www.openapis.org/">Open API&lt;/a> based web API using &lt;a href="https://cloud.google.com/endpoints/">Cloud Endpoints&lt;/a>, on the Google Cloud platform.&lt;/p>
&lt;p>I spoke about the topic a few times, as web APIs is a topic I enjoy, at Nordic APIs, at APIDays, or Devoxx. But it&amp;rsquo;s great to see the video online. So let me share the slide deck along with the &lt;a href="https://www.infoq.com/presentations/web-api-scale-google-cloud">video&lt;/a>:&lt;/p></description><content:encoded>
<![CDATA[<p>InfoQ recently released a <a href="https://www.infoq.com/presentations/web-api-scale-google-cloud">video</a> from the <a href="http://paris.apidays.io/">APIDays</a> conference that took place in Paris last year. I talked about scaling an <a href="https://www.openapis.org/">Open API</a> based web API using <a href="https://cloud.google.com/endpoints/">Cloud Endpoints</a>, on the Google Cloud platform.</p>
<p>I spoke about the topic a few times, as web APIs is a topic I enjoy, at Nordic APIs, at APIDays, or Devoxx. But it&rsquo;s great to see the video online. So let me share the slide deck along with the <a href="https://www.infoq.com/presentations/web-api-scale-google-cloud">video</a>:</p>
<script async class="speakerdeck-embed" data-id="490a2aa1c3c142d0ac2c40ea60af2cf5" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>In a nutshell, the API contract is the source of truth. Whether you&rsquo;re the one implementing the API backend, or you&rsquo;re the consumer calling the API, there&rsquo;s this central contract that each party can rely on, to be certain how the API should be looking like, what kind of endpoint to expect, what payloads will be exchanged, or which status codes are used.</p>
<p>With a central contract, team communication and collaboration is facilitated: I&rsquo;ve seen customers where a central architecture team would define a contract, that was implemented by a third-party (an outsourcing consulting company), and the API was consumed by different teams, both internally and externally. The central contract was here to facilitate the work between those teams, to ensure the contract would be fulfilled.</p>
<p>In addition, having such a computer-friendly contract is really useful for tooling. Out of the contract, you can generate various useful artifacts, such as:</p>
<ul>
<li><strong>static &amp; live mocks</strong> &mdash; that consumers can use when the API is not finalized, </li>
<li><strong>test stubs</strong> &mdash; for facilitating integration tests, </li>
<li><strong>server skeletons</strong> &mdash; to get started implementing the business logic of the API with a ready-made project template,</li>
<li><strong>client SDKs</strong> &mdash; offering kits consumers can use, using various languages, to call your API more easily,</li>
<li><strong>sandbox &amp; live playground</strong> &mdash; a visual environment for testing and calling the API, for developers to discover how the API actually works,</li>
<li><strong>an API portal with provisioning</strong> &mdash; a website offering the API reference documentation and allowing developers to get credentials to get access to the API,</li>
<li><strong>static documentation</strong> &mdash; perhaps with just the API reference documentation, or a bundle of useful associated user guide, etc.</li>
</ul>
<p>However, be careful with artifact generation. As soon as you start making some customizations to what&rsquo;s been generated by tools, you might run the risk of overwriting those changes the next time you re-generate those artifacts! So beware, how customization can be done and be integrated with those generated artifacts.</p>
<p>In my presentation and demo, I decided to use Cloud Endpoints to manage my API, and to host the business logic of my API implementation on the Google Cloud Platform. GCP (for short) provides various &ldquo;compute&rdquo; solutions for your projects:</p>
<ul>
<li><a href="https://cloud.google.com/appengine/">Google App Engine</a> (Platform-as-a-Service): you deploy your code, and all the scaling is done transparently for you by the platform,</li>
<li><a href="https://cloud.google.com/container-engine/">Google Container Engine</a> (Container-as-a-Service): it&rsquo;s a Kubernetes-based container orchestrator where you deploy your apps in the form of containers,</li>
<li><a href="https://cloud.google.com/compute/">Google Compute Engine</a> (Infrastructure-as-a-Service): this time, it&rsquo;s full VMs, with even more control on the environment, that you deploy and scale.</li>
</ul>
<p>In my case, I went with a containerized <a href="https://ratpack.io/">Ratpack</a> implementation for my API, implemented using the <a href="http://www.groovy-lang.org/">Apache Groovy</a> programming language (what else? :-). So I deployed my application on Container Engine.</p>
<p>I described my web API via an Open API descriptor, and managed it via Cloud Endpoints. Cloud Endpoints is actually the underlying infrastructure used by Google themselves, to host all the APIs developers can use today (think Google Maps API, etc.) This architecture already serves literally hundreds of billions of requests everyday&hellip; so you can assume it&rsquo;s certainly quite scalable in itself. You can manage APIs described with Open API, regardless of how they were implemented (totally agnostic from the underlying implementation), and it can manage both HTTP-based JSON web APIs, as well as <a href="https://grpc.io/">gRPC</a> based ones.</p>
<p>There are three interesting key aspects to know about Cloud Endpoints, regardless of whether you&rsquo;re using the platform for public / private / mobile / micro-services APIs:</p>
<ul>
<li>Cloud Endpoints takes care of security, to control access to the API, to authenticate consumers (taking advantage of API keys, Firebase auth, Auth0, JSON Web Tokens)</li>
<li>Cloud Endpoints offers logging and monitoring capabilities of key API related metrics</li>
<li>Cloud Endpoints is super snappy and scales nicely as already mentioned (we&rsquo;ll come back to this in a minute)</li>
</ul>
<p>Cloud Endpoints actually offers an <a href="https://github.com/cloudendpoints/esp/">open source &ldquo;sidecar&rdquo; container proxy</a>. Your containerized application will go hand in hand with the <a href="https://cloud.google.com/endpoints/docs/running-esp-localdev">Extensible Service Proxy</a>, and will actually be wrapped by that proxy. All the calls will actually go through that proxy before hitting your own application. Interestingly, there&rsquo;s not one single proxy, but each instance of you app will have its own proxy, thus diminishing the latency between the call to the proxy and the actual code execution in your app (there&rsquo;s no network hop between the two, to a somewhat distant central proxy, as the two containers are together). For the record, this proxy is based on <a href="https://www.nginx.com/">Nginx</a>. And that proxy container can also be run elsewhere, even on your own infrastructure.</p>
<p>In summary, Cloud Endpoints takes care of securing, monitoring and scaling your Web API. Developing, deploying, and managing your API on Google Cloud Platform gives you the choice: in terms of protocol with JSON / HTTP based APIs or gRPC, in terms of implementation technology as you can chose any language or framework you wish that are supported by the various compute options of the platform allow you to go from PaaS, to CaaS, or IaaS. Last but not least, this solution is open: based on open standards like Open API and gRPC, or by implementing its proxy on top of Nginx.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Scale Jenkins with Kubernetes on Google Container Engine</title><link>https://glaforge.dev/talks/2017/07/17/scale-jenkins-with-kubernetes-on-google-container-engine/</link><pubDate>Mon, 17 Jul 2017 10:32:40 +0100</pubDate><guid>https://glaforge.dev/talks/2017/07/17/scale-jenkins-with-kubernetes-on-google-container-engine/</guid><description>&lt;p>Last week, I had the pleasure to speak at the &lt;a href="https://jcd-paris.jfrog.com/">Jenkins Community Day&lt;/a> conference, in Paris, organized by my friends from &lt;a href="https://www.jfrog.com/">JFrog&lt;/a>, provider of awesome tools for software management and distribution. I covered how to scale &lt;a href="https://jenkins.io/">Jenkins&lt;/a> with &lt;a href="https://kubernetes.io/">Kubernetes&lt;/a> on &lt;a href="https://cloud.google.com/container-engine/">Google Container Engine&lt;/a>.&lt;/p>
&lt;p>For the impatient, here are the slides of the presentation I&amp;rsquo;ve given:&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="47af08ec8f0b40e48335ff0390270b28" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>But let&amp;rsquo;s step back a little. In this article, I&amp;rsquo;d like to share with you why you would want to run Jenkins in the cloud, as well as give you some pointers to interesting resources on the topic.&lt;/p></description><content:encoded>
<![CDATA[<p>Last week, I had the pleasure to speak at the <a href="https://jcd-paris.jfrog.com/">Jenkins Community Day</a> conference, in Paris, organized by my friends from <a href="https://www.jfrog.com/">JFrog</a>, provider of awesome tools for software management and distribution. I covered how to scale <a href="https://jenkins.io/">Jenkins</a> with <a href="https://kubernetes.io/">Kubernetes</a> on <a href="https://cloud.google.com/container-engine/">Google Container Engine</a>.</p>
<p>For the impatient, here are the slides of the presentation I&rsquo;ve given:</p>
<script async class="speakerdeck-embed" data-id="47af08ec8f0b40e48335ff0390270b28" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>But let&rsquo;s step back a little. In this article, I&rsquo;d like to share with you why you would want to run Jenkins in the cloud, as well as give you some pointers to interesting resources on the topic.</p>
<h2 id="why-running-jenkins-in-the-cloud">Why running Jenkins in the cloud?</h2>
<p>So why running Jenkins in the cloud? First of all, imagine your small team, working on a single project. You have your own little server, running under a desk somewhere, happily building your application on each commit, a few times a day. So far so good, your build machine running Jenkins isn&rsquo;t too busy, and stays idle most of the day.</p>
<p>Let&rsquo;s do some bottom of the napkin calculations. Let&rsquo;s say you have a team of 3 developers, committing roughly 4 times a day, on one single project, and the build takes roughly 10 minutes to go.</p>
<p>3 developers * 4 commits / day / developer * 10 minutes build time * 1 project = 1 hour 20 minutes</p>
<p>So far so good, your server indeed stays idle most of the day. Usually, at most, your developers will wait just 10 minutes to see the result of their work.</p>
<p>But your team is growing to 10 persons, the team is still as productive, but the project becoming bigger, the build time goes up to 15 minutes:</p>
<p>10 developers * 4 commits / day / developer * 15 minutes build time * 1 project = 10 hours</p>
<p>You&rsquo;re already at 10 hours build time, so your server is busy the whole day, and at times, you might have several build going on at the same time, using several CPU cores in parallel. And instead of building in 15 minutes, sometimes, the build might take longer, or your build might be queued. So in theory, it might be 15 minutes, but in practice, it could be half an hour because of the length of the queue or the longer time to build parallel projects.</p>
<p>Now, the company is successful, and has two projects instead of one (think a backend and a mobile app). Your teams grow further up to 20 developers per project. The developers are a little less productive because of the size of the codebase and project, so they only commit 3 times a day. The build takes more time too, at 20 minutes (in ideal time). Let&rsquo;s do some math again:</p>
<p>20 developers * 3 commits / day / developer * 20 minutes build time * 2 projects = 40 hours</p>
<p>Woh, that&rsquo;s already 40 hours of total build time, if all the builds are run serially. Fortunately, our server is multi-core, but still, there are certainly already many builds that are enqueued, and many of them, perhaps up to 2-3 or perhaps even 4 could be run in parallel. But as we said, the build queue increases further, the real effective time of build is certainly longer than 30 minutes. Perhaps at times, developers won&rsquo;t see the result of their developments before at least an hour, if not more.</p>
<p>One last calculation? With team sizes of 30 developers, decreased productivity of 2 commits, 25 build time, and 3 projects? And you&rsquo;ll get 75 hours total build time. You may start creating a little build farm, with a master and several build agents. But you also increase the burden of server management. Also, if you move towards a full Continuous Delivery or Continuous Deployment approach, you may further increase your build times to go up to deployment, make more but smaller commits, etc. You could think of running builds less often, or even on a nightly basis, to cope with the demand, but then, your company is less agile, and the time-to-market for fixes of new features might increase, and your developers may also become more frustrated because they are developing in the blind, not knowing before the next day if their work was successful or not.</p>
<p>With my calculations, you might think that it makes more sense for big companies, with tons of projects and developers. This is quite true, but when you&rsquo;re a startup, you also want to avoid taking care of local server management, provisioning, etc. You want to be agile, and use only compute resources you need for the time you need them. So even if you&rsquo;re a small startup, a small team, it might still make sense to take advantage of the cloud. You pay only for the actual time taken by your builds as the build agent containers are automatically provisioned and decommissioned. The builds can scale up via Kubernetes, as you need more (or less) CPU time for building everything.</p>
<p>And this is why I was happy to dive into scaling Jenkins in the cloud. For that purpose, I decided to go with building with containers, with <a href="https://kubernetes.io/">Kubernetes</a>, as my app was also containerized as well. <a href="https://cloud.google.com/">Google Cloud</a> offers <a href="https://cloud.google.com/container-engine/">Container Engine</a>, which is basically just Kubernetes in the cloud.</p>
<h2 id="useful-pointers">Useful pointers</h2>
<p>I based my presentation and demo on some great solutions that are published on the Google Cloud documentation portal. Let me give you some pointers:</p>
<ul>
<li><a href="https://cloud.google.com/solutions/jenkins-on-container-engine">Overview of Jenkins on Container Engine</a></li>
<li><a href="https://cloud.google.com/solutions/jenkins-on-container-engine-tutorial">Setting up Jenkins on Container Engine</a></li>
<li><a href="https://cloud.google.com/solutions/configuring-jenkins-container-engine">Configuring Jenkins for Container Engine</a></li>
<li><a href="https://cloud.google.com/solutions/continuous-delivery-jenkins-container-engine">Continuous Deployment to Container Engine using Jenkins</a></li>
<li><a href="https://github.com/GoogleCloudPlatform/continuous-deployment-on-kubernetes">Lab: Build a Continuous Deployment Pipeline with Jenkins and Kubernetes</a></li>
</ul>
<p>The latter one is the tutorial I actually followed for the demo that I presented during the conference. It&rsquo;s a simple Go application, with a frontend and backend. It&rsquo;s continuously build, on each commit (well, every minute to check if there&rsquo;s a new commit), and deployed automatically in different environments: dev, canary, production. The sources of the project are stored in <a href="https://cloud.google.com/source-repositories/">Cloud Source Repository</a> (it can be mirrored from Github, for example). The containers are stored in <a href="https://cloud.google.com/container-registry/">Cloud Container Registry</a>. And both the Jenkins master and agents, as well as the application are running inside Kubernetes clusters in <a href="https://cloud.google.com/container-engine/">Container Engine</a>.</p>
<h2 id="summary-and-perspective">Summary and perspective</h2>
<p>Don&rsquo;t bother with managing servers! Quickly, you&rsquo;ll run out of CPU cycles, and you&rsquo;ll have happier developers with builds that are super snappy!</p>
<p>And for the record, at Google, dev teams are also running Jenkins! There was a presentation (<a href="https://www.youtube.com/watch?v=7ERV9C20GSE">video</a> and <a href="https://www.cloudbees.com/sites/default/files/2016-jenkins-world-jenkins_inside_google.pdf">slides</a> available) given last year by David Hoover at Jenkins World talking about how developers inside Google are running hundreds of build agents to build projects on various platforms.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A year as a Google Cloud Developer Advocate</title><link>https://glaforge.dev/posts/2017/06/13/a-year-as-a-google-cloud-developer-advocate/</link><pubDate>Tue, 13 Jun 2017 11:34:15 +0100</pubDate><guid>https://glaforge.dev/posts/2017/06/13/a-year-as-a-google-cloud-developer-advocate/</guid><description>&lt;p>Time flies! Last week was my first &amp;ldquo;Googleversary&amp;rdquo;: It&amp;rsquo;s already been a year since I joined Google Cloud as a Developer Advocate. What a ride it&amp;rsquo;s been so far!&lt;/p>
&lt;p>I announced my &lt;a href="https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/">move to Google&lt;/a> in June last year. And since then got the chance to:&lt;/p>
&lt;ul>
&lt;li>talk at more than 20 conferences or meetups&lt;/li>
&lt;li>give 3 keynotes&lt;/li>
&lt;li>write 36 articles&lt;/li>
&lt;li>meet with a dozen customers or so&lt;/li>
&lt;li>addressed literally thousands of developers&lt;/li>
&lt;/ul>
&lt;p>For some conferences, like Devoxx Belgium, I even spoke 5 times! Or for my &lt;a href="https://glaforge.dev/posts/2017/05/16/flying-east-to-singapore/">trip to Singapore&lt;/a>, I had 6 talks or workshops lined up!&lt;/p></description><content:encoded>
<![CDATA[<p>Time flies! Last week was my first &ldquo;Googleversary&rdquo;: It&rsquo;s already been a year since I joined Google Cloud as a Developer Advocate. What a ride it&rsquo;s been so far!</p>
<p>I announced my <a href="https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/">move to Google</a> in June last year. And since then got the chance to:</p>
<ul>
<li>talk at more than 20 conferences or meetups</li>
<li>give 3 keynotes</li>
<li>write 36 articles</li>
<li>meet with a dozen customers or so</li>
<li>addressed literally thousands of developers</li>
</ul>
<p>For some conferences, like Devoxx Belgium, I even spoke 5 times! Or for my <a href="https://glaforge.dev/posts/2017/05/16/flying-east-to-singapore/">trip to Singapore</a>, I had 6 talks or workshops lined up!</p>
<p>There&rsquo;s a great level of freedom in this job, and you can decide where to put the cursor in terms of travel, or where to put the puck regarding the topics and products to cover. This is really wonderful!</p>
<p>I had the chance to cover things like Google <a href="https://cloud.google.com/appengine/">App Engine</a> (you know I&rsquo;ve been a big fan of it since 2009!), <a href="https://kubernetes.io/">Kubernetes</a> / <a href="https://cloud.google.com/container-engine/">Container Engine</a>, or <a href="https://cloud.google.com/functions/">Cloud Functions</a>, on the compute side of the story, with a bit of <a href="https://cloud.google.com/endpoints/">Cloud Endpoints</a> for the Web API aspect. And on the Big Data / ML side, I talked about the various <a href="https://cloud.google.com/products/machine-learning/">Machine Learning APIs</a> (Vision, Speech recognition, Natural Language processing, Video intelligence), or played with <a href="https://cloud.google.com/bigquery/">BigQuery</a> for analyzing the Github dataset. And lately, my big hit at conferences and meetups, it&rsquo;s been the hot topic of chatbots + serverless, with <a href="https://api.ai/">API.AI</a> and Cloud Functions for building conversational interfaces.</p>
<p>But there are just so many hours in a day, and there&rsquo;s so much more I want to work on and play with! As I often tell friends and developers that I meet here and there: working as a developer advocate for Google Cloud, it&rsquo;s a bit like being a kid entering a toy store where you&rsquo;d be told: &ldquo;hey, here are all the toys we have, feel free to play with everything you want!&rdquo; But it&rsquo;s not only the toys&hellip; you also get the chance to play with other kids (err, sorry, fellow DAs) on all those things, to imagine the next epic game together!</p>
<p>I&rsquo;m looking forward to telling you more about Google Cloud! Let&rsquo;s meet and chat at the next conference or meetup!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Trying out Apache Groovy's new Antlr4 parser with Java 8 support</title><link>https://glaforge.dev/posts/2017/05/29/trying-out-apache-groovy-new-antlr4-parser-with-java-8-support/</link><pubDate>Mon, 29 May 2017 11:37:52 +0100</pubDate><guid>https://glaforge.dev/posts/2017/05/29/trying-out-apache-groovy-new-antlr4-parser-with-java-8-support/</guid><description>&lt;p>&lt;a href="http://www.groovy-lang.org/">Apache Groovy&lt;/a> is coming up with a new parser, that supports the Java 8 syntax elements, as well as some new notation and operators of its own (like !in, !instanceof or ?[] for safe navigation with collections, or with ?= for Elvis assignment). I blogged recently about the fact that you can &lt;a href="https://glaforge.dev/posts/2017/03/24/testing-java-8-snippets-on-the-new-app-engine-java-8-runtime/">try this new flavor online on this forked Groovy Web Console&lt;/a> version, without the need of installing everything. But today I&amp;rsquo;ll tell you how to build it for yourself in order to run it on your machine.&lt;/p></description><content:encoded>
<![CDATA[<p><a href="http://www.groovy-lang.org/">Apache Groovy</a> is coming up with a new parser, that supports the Java 8 syntax elements, as well as some new notation and operators of its own (like !in, !instanceof or ?[] for safe navigation with collections, or with ?= for Elvis assignment). I blogged recently about the fact that you can <a href="https://glaforge.dev/posts/2017/03/24/testing-java-8-snippets-on-the-new-app-engine-java-8-runtime/">try this new flavor online on this forked Groovy Web Console</a> version, without the need of installing everything. But today I&rsquo;ll tell you how to build it for yourself in order to run it on your machine.</p>
<p>It&rsquo;s still to be decided which is going to be the version number of the release containing the new &ldquo;parrot&rdquo; parser, but you can already play with this syntax today. As I&rsquo;m the kind of guy living on the edge of Groovy, I always use Groovy from the firehose, using the master branch with the latest and greatest changes. But I always forget about the right parameters or environment variable to use to build Groovy with the new parser, and to activate it&hellip; although it&rsquo;s clearly <a href="https://github.com/apache/groovy/tree/master/subprojects/parser-antlr4#how-to-enable-the-new-parser">explained in the documentation</a>. So as a note to self, to know where to look at, I decided to write it down in this post!</p>
<p>Build Apache Groovy from the master branch with the Antlr4 parser:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew -PuseAntlr4<span style="color:#666">=</span><span style="color:#007020">true</span> installGroovy
</span></span></code></pre></div><p>It&rsquo;s going to build an installation of Groovy that you can point <a href="http://sdkman.io/">SDKman</a> at:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sdk install groovy target/install/ dev
</span></span><span style="display:flex;"><span>$ sdk use groovy dev
</span></span></code></pre></div><p>That way, you can use this &ldquo;dev&rdquo; version of Groovy in your shell. However, you still need to enable the Antlr4 parser, and you can do so with the following exported environment variable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ <span style="color:#007020">export</span> <span style="color:#bb60d5">JAVA_OPTS</span><span style="color:#666">=</span>-Dgroovy.antlr4<span style="color:#666">=</span><span style="color:#007020">true</span>
</span></span></code></pre></div><p>Then, when running groovyConsole or groovysh, you&rsquo;ll be able to try the new syntax.</p>
<p>To learn more about the new parser, you can have a look at my presentation with some of the novelties provided by the &ldquo;parrot&rdquo; parser here:</p>
<script async class="speakerdeck-embed" data-id="cdbf0cee31fd4eeca3c4add6ad86b3b0" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>And you can read Sergio&rsquo;s translation of Daniel&rsquo;s article on the new features <a href="http://sergiodelamo.es/preview-of-groovy-3/">here</a>.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Flying East to Singapore</title><link>https://glaforge.dev/posts/2017/05/16/flying-east-to-singapore/</link><pubDate>Tue, 16 May 2017 11:40:06 +0100</pubDate><guid>https://glaforge.dev/posts/2017/05/16/flying-east-to-singapore/</guid><description>&lt;p>In two weeks, I&amp;rsquo;ll be flying east, much further east than I&amp;rsquo;ve ever been! I&amp;rsquo;ll visit Singapore! And I&amp;rsquo;ll have a pretty busy week with several events: conference, meetup, user groups, brown bag lunch&amp;hellip; and I&amp;rsquo;ll talk about Groovy, Machine Learning, and chatbots!&lt;/p>
&lt;p>First of all, on Wednesday 31st, I&amp;rsquo;ll participate to the &lt;a href="https://www.meetup.com/fr-FR/singajug/events/240026369/?eventId=240026369">Singapore Java User Group&lt;/a>, where I&amp;rsquo;ll give an update on &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a> (the latest improvements, new features, the roadmap).&lt;/p></description><content:encoded>
<![CDATA[<p>In two weeks, I&rsquo;ll be flying east, much further east than I&rsquo;ve ever been! I&rsquo;ll visit Singapore! And I&rsquo;ll have a pretty busy week with several events: conference, meetup, user groups, brown bag lunch&hellip; and I&rsquo;ll talk about Groovy, Machine Learning, and chatbots!</p>
<p>First of all, on Wednesday 31st, I&rsquo;ll participate to the <a href="https://www.meetup.com/fr-FR/singajug/events/240026369/?eventId=240026369">Singapore Java User Group</a>, where I&rsquo;ll give an update on <a href="http://groovy-lang.org/">Apache Groovy</a> (the latest improvements, new features, the roadmap).</p>
<p><figure>
  <a href="#img-79424708b4386e138d3efe736a897c78">
    <img src="/img/sin/sin-jug-groovy.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-79424708b4386e138d3efe736a897c78">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sin/sin-jug-groovy.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Thursday 1st, I&rsquo;ll visit <a href="https://www.ca-cib.com/our-global-presence/asia-pacific/singapore">Crédit Agricole CIB</a>, for a brown-bag lunch about Machine Learning. They did a fun survey to decide which topics I should cover! And funnily, this is in this office that is maintained and developed a banking project on which I worked more than ten years before!</p>
<p><figure>
  <a href="#img-419c37ec74a7e0ece9c4137d827c4851">
    <img src="/img/sin/sin-ca.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-419c37ec74a7e0ece9c4137d827c4851">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sin/sin-ca.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then, it&rsquo;s Voxxed Days Singapore time! I&rsquo;ll talk again about the Machine Learning APIs offered by Google Cloud, as well as a few words about TensorFlow and Cloud Machine Learning Engine.</p>
<p><figure>
  <a href="#img-eb531abb3b892625aff5eeae0d7951cf">
    <img src="/img/sin/sin-voxxed.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-eb531abb3b892625aff5eeae0d7951cf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sin/sin-voxxed.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then, on Saturday 3rd, two events, one organized by the GDG Singapore, which will be a <a href="http://peatix.com/event/263477/view">workshop on building chatbots</a>, using API.AI and Cloud Functions, and covering the Google Home and Assistant too.</p>
<p><figure>
  <a href="#img-de7eb702bcb50b13e029cc3d1f12a4eb">
    <img src="/img/sin/sin-gdg-chatbot.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-de7eb702bcb50b13e029cc3d1f12a4eb">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sin/sin-gdg-chatbot.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And the last event will be held in Google&rsquo;s offices, on the theme of <a href="https://events.withgoogle.com/going-beyond-data-science/">data science</a>, where I&rsquo;ll will also give a presentation on chatbots, the Google Assistant with Google Home, with API.AI for the conversational interface, and Cloud Functions for the business logic.</p>
<p><figure>
  <a href="#img-dbae607f6d133dce3f8cc664bb0a517e">
    <img src="/img/sin/sin-data-science.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-dbae607f6d133dce3f8cc664bb0a517e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sin/sin-data-science.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>It&rsquo;s gonna be a pretty busy week, and I&rsquo;m looking forward to meeting tons of developers in Singapore, as well as reconnect with some of my friends &amp; former colleagues living in the area!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Machine Learning and Scaling Web Apis</title><link>https://glaforge.dev/talks/2017/05/10/machine-learning-and-scaling-web-apis/</link><pubDate>Wed, 10 May 2017 11:43:01 +0100</pubDate><guid>https://glaforge.dev/talks/2017/05/10/machine-learning-and-scaling-web-apis/</guid><description>&lt;p>The &lt;a href="https://jax.de/">JAX conference&lt;/a>, in Mainz, Germany, is coming to an end. I was there with my colleagues &lt;a href="https://twitter.com/meteatamel">Mete&lt;/a> and &lt;a href="https://twitter.com/hostirosti">Robert&lt;/a>, and had the chance to cover two topics: Machine Learning and Scaling Web APIs. It&amp;rsquo;s a pleasure to come back to this conference where the audience is always very focused, eager to learn, and is engaging in great and interesting conversations.&lt;/p>
&lt;h2 id="machine-intelligence-at-google-scale">Machine Intelligence at Google Scale&lt;/h2>
&lt;p>My &lt;a href="https://jax.de/session/machine-intelligence-at-google-scale-visionspeech-api-tensorflow-and-cloud-machine-learning/">first presentation&lt;/a> was about Machine Learning, and in particular with the Google Cloud APIs, including &lt;a href="https://cloud.google.com/vision/">Vision&lt;/a>, &lt;a href="https://cloud.google.com/speech/">Speech&lt;/a>, &lt;a href="https://cloud.google.com/natural-language/">Natural Language&lt;/a>, &lt;a href="https://cloud.google.com/translate/">Translate&lt;/a>, and &lt;a href="https://cloud.google.com/video-intelligence/">Video Intelligence&lt;/a>. Although I&amp;rsquo;m not an expert in &lt;a href="https://www.tensorflow.org/">TensorFlow&lt;/a> and the &lt;a href="https://cloud.google.com/ml-engine/">Cloud Machine Learning Engine&lt;/a>, I got a chance to say a few words about these. I guess I&amp;rsquo;ll have to play with both at some point to be able to tell even more!&lt;/p></description><content:encoded>
<![CDATA[<p>The <a href="https://jax.de/">JAX conference</a>, in Mainz, Germany, is coming to an end. I was there with my colleagues <a href="https://twitter.com/meteatamel">Mete</a> and <a href="https://twitter.com/hostirosti">Robert</a>, and had the chance to cover two topics: Machine Learning and Scaling Web APIs. It&rsquo;s a pleasure to come back to this conference where the audience is always very focused, eager to learn, and is engaging in great and interesting conversations.</p>
<h2 id="machine-intelligence-at-google-scale">Machine Intelligence at Google Scale</h2>
<p>My <a href="https://jax.de/session/machine-intelligence-at-google-scale-visionspeech-api-tensorflow-and-cloud-machine-learning/">first presentation</a> was about Machine Learning, and in particular with the Google Cloud APIs, including <a href="https://cloud.google.com/vision/">Vision</a>, <a href="https://cloud.google.com/speech/">Speech</a>, <a href="https://cloud.google.com/natural-language/">Natural Language</a>, <a href="https://cloud.google.com/translate/">Translate</a>, and <a href="https://cloud.google.com/video-intelligence/">Video Intelligence</a>. Although I&rsquo;m not an expert in <a href="https://www.tensorflow.org/">TensorFlow</a> and the <a href="https://cloud.google.com/ml-engine/">Cloud Machine Learning Engine</a>, I got a chance to say a few words about these. I guess I&rsquo;ll have to play with both at some point to be able to tell even more!</p>
<blockquote>
<p>The biggest challenge of Deep Learning technology is the scalability. As long as using single GPU server, you have to wait for hours or days to get the result of your work. This doesn&rsquo;t scale for production service, so you need a Distributed Training on the cloud eventually. Google has been building infrastructure for training the large scale neural network on the cloud for years, and now started to share the technology with external developers. In this session, we will introduce new pre-trained ML services such as Cloud Vision API and Speech API that works without any training. Also, we will look how TensorFlow and Cloud Machine Learning will accelerate custom model training for 10x &ndash; 40x with Google&rsquo;s distributed training infrastructure.</p></blockquote>
<script async class="speakerdeck-embed" data-id="b3fe3f4eb13547fc8e81785c54aceafd" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="scale-a-swagger-based-web-apiwith-google-cloud-endpoints">Scale a Swagger-based Web API with Google Cloud Endpoints</h2>
<p>My <a href="https://jax.de/session/scale-a-swagger-based-web-api/">second session</a> was about scaling web APIs, defined using the <a href="https://www.openapis.org/">Open API specification</a>, thanks to <a href="https://kubernetes.io/">Kubernetes</a> and <a href="https://cloud.google.com/container-engine/">Google Container Engine</a>, for the scaling part of the story, and managing those APIs thanks to <a href="https://cloud.google.com/endpoints/">Google Cloud Endpoints</a>.</p>
<blockquote>
<p> Web APIs are more often specified with API definition languages like Swagger (now named OpenAPI Spec), as it can help you generate nice interactive documentation, server skeletons, and client SDKs, mocks, and more, making it simpler to get started both producing and consuming an API. In this session, Guillaume will demonstrate how to define a Web API with Swagger/OpenAPI Spec, and scale it using Cloud Endpoints, on the Google Cloud Platform.</p></blockquote>
<script async class="speakerdeck-embed" data-id="490a2aa1c3c142d0ac2c40ea60af2cf5" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A chatbot for the Devoxx conference agenda with API.ai and Cloud Functions</title><link>https://glaforge.dev/talks/2017/04/06/a-chatbot-for-the-devoxx-conference-agenda-with-apiai-and-cloud-functions/</link><pubDate>Thu, 06 Apr 2017 11:51:44 +0100</pubDate><guid>https://glaforge.dev/talks/2017/04/06/a-chatbot-for-the-devoxx-conference-agenda-with-apiai-and-cloud-functions/</guid><description>&lt;p>That&amp;rsquo;s Devoxx France this week, and I&amp;rsquo;ve had the pleasure of delivering today another &lt;a href="https://cfp.devoxx.fr/2017/talk/NSJ-9765/Un_bot_pour_gerer_l'agenda_de_ta_conference">talk on the theme of chatbots, using Cloud Functions for the business logic, API.AI for the bot cleverness&lt;/a>, with a bonus of a demo through Google Home and the Google Assistant platform.&lt;/p>
&lt;p>I&amp;rsquo;ll post the YouTube video recording once it&amp;rsquo;s online, but in the meantime, I wanted to share my slides here:&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="5bbe77c6ff1e4f0da337c7328c75936a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>And the video in French:&lt;/p></description><content:encoded>
<![CDATA[<p>That&rsquo;s Devoxx France this week, and I&rsquo;ve had the pleasure of delivering today another <a href="https://cfp.devoxx.fr/2017/talk/NSJ-9765/Un_bot_pour_gerer_l'agenda_de_ta_conference">talk on the theme of chatbots, using Cloud Functions for the business logic, API.AI for the bot cleverness</a>, with a bonus of a demo through Google Home and the Google Assistant platform.</p>
<p>I&rsquo;ll post the YouTube video recording once it&rsquo;s online, but in the meantime, I wanted to share my slides here:</p>
<script async class="speakerdeck-embed" data-id="5bbe77c6ff1e4f0da337c7328c75936a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>And the video in French:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/vNoiTnnlGC0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Testing Java 8 Snippets on the new App Engine Java 8 runtime</title><link>https://glaforge.dev/posts/2017/03/24/testing-java-8-snippets-on-the-new-app-engine-java-8-runtime/</link><pubDate>Fri, 24 Mar 2017 11:57:41 +0100</pubDate><guid>https://glaforge.dev/posts/2017/03/24/testing-java-8-snippets-on-the-new-app-engine-java-8-runtime/</guid><description>&lt;p>A new Java 8 runtime for &lt;a href="https://cloud.google.com/appengine/docs/standard/">Google App Engine standard&lt;/a> is coming soon, and is currently in alpha testing. You can &lt;a href="https://docs.google.com/a/google.com/forms/d/1MDzykTWp77YzRgFs5R6ONOuKWYnKEhfy5VhSJYbDvmo/viewform?edit_requested=true">request to join&lt;/a> the alpha program, if you want to try it out for yourself. But I wanted to let anyone play with it, easily, to see how well the Java 8 APIs work, but also to try some Java 8 syntax too. So here&amp;rsquo;s a &lt;a href="https://cafe-huitre.appspot.com/">web console&lt;/a> where you can do just that!&lt;/p></description><content:encoded>
<![CDATA[<p>A new Java 8 runtime for <a href="https://cloud.google.com/appengine/docs/standard/">Google App Engine standard</a> is coming soon, and is currently in alpha testing. You can <a href="https://docs.google.com/a/google.com/forms/d/1MDzykTWp77YzRgFs5R6ONOuKWYnKEhfy5VhSJYbDvmo/viewform?edit_requested=true">request to join</a> the alpha program, if you want to try it out for yourself. But I wanted to let anyone play with it, easily, to see how well the Java 8 APIs work, but also to try some Java 8 syntax too. So here&rsquo;s a <a href="https://cafe-huitre.appspot.com/">web console</a> where you can do just that!</p>
<p><a href="https://cafe-huitre.appspot.com/"><figure>
  <a href="#img-10ecd4dcda6274619ff850f74a16dfa6">
    <img src="/img/j8snip/groovy-web-console-java8.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-10ecd4dcda6274619ff850f74a16dfa6">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/j8snip/groovy-web-console-java8.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<p>But to be precise, it&rsquo;s actually my good old <a href="http://groovyconsole.appspot.com/">Groovy Web Console</a>, where people can write, execute and save <a href="http://www.groovy-lang.org/">Apache Groovy</a> snippets. It is a special version, in fact, as it&rsquo;s built on Java 8, uses the invoke dynamic flavor, and&hellip; drum roll&hellip; it&rsquo;s using the upcoming &ldquo;Parrot&rdquo; parser which adds the Java 8 syntax constructs to the Groovy grammar. So not only can you try Java snippets, but it&rsquo;s a great opportunity to try the future Groovy parser that&rsquo;s gonna be released in Apache Groovy 2.5 or 3.0 (still to be decided).</p>
<p>A meetup about Java 8 on Google App Engine standard</p>
<p>Also, for those who live in Paris and the area, we have the chance of having <a href="https://twitter.com/ludoch">Ludovic Champenois</a>, an engineer working on App Engine, that will be in France, and will be speaking at this <a href="https://www.meetup.com/fr-FR/GDG-Cloud-Paris/events/238519360/?rv=ea1&amp;_af=event&amp;_af_eid=238519360&amp;https=on">GDG Cloud meetup hosted by Xebia</a>, which takes places on Tuesday, April 4th, just on the even of Devoxx France!</p>
<p>So if you want to learn more about Java 8 on App Engine, please <a href="https://www.meetup.com/fr-FR/GDG-Cloud-Paris/events/238519360/?rv=ea1&amp;_af=event&amp;_af_eid=238519360&amp;https=on">sign up</a>!</p>
<p>I will also be presenting about <a href="https://madeby.google.com/home/">Google Home</a>, the <a href="https://assistant.google.com/">Google Assistant</a>, <a href="https://api.ai/">API.AI</a>, and Google <a href="https://cloud.google.com/functions/">Cloud Functions</a> to host the logic of your very own bots and agents. It&rsquo;s based on the <a href="https://glaforge.dev/talks/2017/03/13/extending-the-google-assistant-with-actions-on-google/">presentation I gave at Cloud Next</a> 2017 in San Francisco. If you want to learn more about</p>
<p><a href="https://www.meetup.com/fr-FR/GDG-Cloud-Paris/events/238519360/?rv=ea1&amp;_af=event&amp;_af_eid=238519360&amp;https=on"><figure>
  <a href="#img-ffe28be64ed48027d59ef44b43e53ac1">
    <img src="/img/j8snip/gae-home-cf-xebia.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-ffe28be64ed48027d59ef44b43e53ac1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/j8snip/gae-home-cf-xebia.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Happy Pi Day! Google Home helps you learn the digits of Pi</title><link>https://glaforge.dev/posts/2017/03/14/happy-pi-day-google-home-helps-you-learn-the-digits-of-pi/</link><pubDate>Tue, 14 Mar 2017 12:10:23 +0100</pubDate><guid>https://glaforge.dev/posts/2017/03/14/happy-pi-day-google-home-helps-you-learn-the-digits-of-pi/</guid><description>&lt;p>You know what? It&amp;rsquo;s Pi Day today! Well, if you follow the American date standard, it&amp;rsquo;s 3.14 today, a nice approximation of Pi. Last year, in a past life, I had &lt;a href="http://restlet.com/company/blog/2016/03/14/win-a-raspberry-pi-3-to-celebrate-pi-day-with-a-pi-api/">played with Pi&lt;/a> already, but this year, my awesome colleagues (&lt;a href="https://twitter.com/saturnism">Ray&lt;/a>, &lt;a href="https://twitter.com/SandeepDinesh">Sandeep&lt;/a>, &lt;a href="https://twitter.com/francesc">Francesc&lt;/a>, &lt;a href="https://twitter.com/IanMLewis">Ian&lt;/a>) have been working on some very cool demos around Pi, with the &amp;ldquo;Pi delivery&amp;rdquo;, at &lt;a href="https://pi.delivery/">https://pi.delivery/&lt;/a>&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-d9aa117e0e1793cbd38fefc95e5fbd5f">
&lt;img src="https://glaforge.dev/img/pi-day/pi-delivery-music.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-d9aa117e0e1793cbd38fefc95e5fbd5f">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/pi-day/pi-delivery-music.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>You can transform the Pi digits in a nice melody, show a D3.js based visualisation of the transitions between digits, you can stream the Pi digits, and more. And you can learn about how it&amp;rsquo;s been &lt;a href="https://pi.delivery/#howcalculating-pi">developed on the Google Cloud Platform&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>You know what? It&rsquo;s Pi Day today! Well, if you follow the American date standard, it&rsquo;s 3.14 today, a nice approximation of Pi. Last year, in a past life, I had <a href="http://restlet.com/company/blog/2016/03/14/win-a-raspberry-pi-3-to-celebrate-pi-day-with-a-pi-api/">played with Pi</a> already, but this year, my awesome colleagues (<a href="https://twitter.com/saturnism">Ray</a>, <a href="https://twitter.com/SandeepDinesh">Sandeep</a>, <a href="https://twitter.com/francesc">Francesc</a>, <a href="https://twitter.com/IanMLewis">Ian</a>) have been working on some very cool demos around Pi, with the &ldquo;Pi delivery&rdquo;, at <a href="https://pi.delivery/">https://pi.delivery/</a></p>
<p><figure>
  <a href="#img-d9aa117e0e1793cbd38fefc95e5fbd5f">
    <img src="/img/pi-day/pi-delivery-music.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d9aa117e0e1793cbd38fefc95e5fbd5f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pi-day/pi-delivery-music.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You can transform the Pi digits in a nice melody, show a D3.js based visualisation of the transitions between digits, you can stream the Pi digits, and more. And you can learn about how it&rsquo;s been <a href="https://pi.delivery/#howcalculating-pi">developed on the Google Cloud Platform</a>.</p>
<p>Ray pinged me to see if we could also create an assistant you can invoke on Google Home, to ask for digits of Pi, as I recently played with <a href="https://glaforge.dev/talks/2017/03/13/extending-the-google-assistant-with-actions-on-google/">Google Home, API.AI and Cloud Functions</a>! And I played with the idea: created a new Cloud Function that invokes the Pi&rsquo;s Web API, designed an assistant in API.AI, and submitted this assistant to the Google Assistant.</p>
<p>You&rsquo;ll be able to ask your Google Home:</p>

            <link rel="stylesheet" href="/css/vendors/admonitions.d762bab02d2df6f4f18be003fbd11e6175139be403be419f4010274d315eeec0.css" integrity="sha256-12K6sC0t9vTxi&#43;AD&#43;9EeYXUTm&#43;QDvkGfQBAnTTFe7sA=" crossorigin="anonymous">
    <div class="admonition info">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
        <span>Example Interaction</span>
      </div>
      <div class="admonition-content">
        <p>Ok Google, talk to Pi Digit Agent.
What is the 34th digit of Pi?</p>
      </div>
    </div><p>And it will tell you that it&rsquo;s 2.</p>
<p>How did I do that, let&rsquo;s first have a look at the Cloud Function, implemented in JavaScript / Node.js:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;pi-assistant&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;version&#34;</span>: <span style="color:#4070a0">&#34;0.0.1&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;private&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;scripts&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;start&#34;</span>: <span style="color:#4070a0">&#34;node index.js&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;deploy&#34;</span>: <span style="color:#4070a0">&#34;rm -rf node_modules; gcloud alpha functions deploy digit --project digit-of-pi-2017-assistant  --trigger-http --stage-bucket gs://digit-of-pi-2017-assistant/&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;Ask for the n-th digit of Pi!&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;main&#34;</span>: <span style="color:#4070a0">&#34;index.js&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;repository&#34;</span>: <span style="color:#4070a0">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;author&#34;</span>: <span style="color:#4070a0">&#34;Guillaume Laforge&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;dependencies&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;actions-on-google&#34;</span>: <span style="color:#4070a0">&#34;^1.0.7&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;node-fetch&#34;</span>: <span style="color:#4070a0">&#34;^1.6.3&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The key things here are the dependencies: I&rsquo;m using the actions-on-google Node module to interact more easily with API.AI and the Assistant, and I&rsquo;m using node-fetch to interact with the Pi Delivery&rsquo;s REST API.</p>
<p>Let&rsquo;s now have a look at the code of our exported digit function in index.js:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> ApiAiAssistant <span style="color:#666">=</span> require(<span style="color:#4070a0">&#39;actions-on-google&#39;</span>).ApiAiAssistant;
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> fetch <span style="color:#666">=</span> require(<span style="color:#4070a0">&#39;node-fetch&#39;</span>);
</span></span><span style="display:flex;"><span><span style="">​</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">function</span> nthDigit(assistant) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">let</span> rank <span style="color:#666">=</span> <span style="color:#007020">parseInt</span>(assistant.getArgument(<span style="color:#4070a0">&#39;rank&#39;</span>).replace(<span style="color:#235388">/,/g</span>, <span style="color:#4070a0">&#39;&#39;</span>));
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#4070a0">`</span><span style="color:#70a0d0">${</span>rank<span style="color:#70a0d0">}</span><span style="color:#4070a0">nth digit`</span>);
</span></span><span style="display:flex;"><span><span style="">​</span>
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// 0 -&gt; 3, 1 -&gt; ., 2 -&gt; 1, 3 -&gt; 4, 4 -&gt; 1, ...
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#60a0b0;font-style:italic">// let&#39;s return 3 for 0th / 1st, and the digit otherwise, 
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#60a0b0;font-style:italic">// to follow natural human numbering and the fact the dot is accounted
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span><span style="">​</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">let</span> start <span style="color:#666">=</span> rank <span style="color:#666">&lt;</span> <span style="color:#40a070">2</span> <span style="color:#666">?</span> <span style="color:#40a070">0</span> <span style="color:#666">:</span> rank;
</span></span><span style="display:flex;"><span><span style="">​</span>
</span></span><span style="display:flex;"><span>    fetch(<span style="color:#4070a0">`https://api.pi.delivery/v1/pi?start=</span><span style="color:#70a0d0">${</span>start<span style="color:#70a0d0">}</span><span style="color:#4070a0">&amp;numberOfDigits=1`</span>)
</span></span><span style="display:flex;"><span>        .then(response =&gt; response.json())
</span></span><span style="display:flex;"><span>        .then(data =&gt; {
</span></span><span style="display:flex;"><span>            assistant.ask(<span style="color:#4070a0">`Digit </span><span style="color:#70a0d0">${</span>rank<span style="color:#70a0d0">}</span><span style="color:#4070a0"> of Pi is </span><span style="color:#70a0d0">${</span>data.content<span style="color:#70a0d0">}</span><span style="color:#4070a0">. Do you want to know a different digit of Pi? Or say cancel to exit.`</span>);
</span></span><span style="display:flex;"><span>        }).<span style="color:#007020;font-weight:bold">catch</span>(err =&gt; {
</span></span><span style="display:flex;"><span>            console.log(err);
</span></span><span style="display:flex;"><span>            assistant.ask(<span style="color:#4070a0">&#39;The ways of Pi are mysterious... Try again, or with another digit? Or say cancel to exit.&#39;</span>);
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="">​</span>
</span></span><span style="display:flex;"><span>exports.digit <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">function</span> (request, response) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">let</span> assistant <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> ApiAiAssistant({request, response});
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">let</span> actionMap <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> Map();
</span></span><span style="display:flex;"><span>    actionMap.set(<span style="color:#4070a0">&#39;nth-digit-intent&#39;</span>, nthDigit);
</span></span><span style="display:flex;"><span>    assistant.handleRequest(actionMap);
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>It&rsquo;s pretty straightforward, we export a digit function, that creates an API.AI assistant, to which we feed an action map pointing at our main intent, for asking for digits. I extract the parameter (ie. the rank of the digit I&rsquo;m interested in), I call the REST API with a fetch() call, and then I return the result with the assistant.ask() call.</p>
<p>In a nutshell, on API.AI&rsquo;s side, my welcome intent greets you, telling you how to use the assistant:</p>
<p><figure>
  <a href="#img-947065394737cd741cbf7a60ecdc40ae">
    <img src="/img/pi-day/pi-welcome-intent.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-947065394737cd741cbf7a60ecdc40ae">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pi-day/pi-welcome-intent.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And then the main intent, whose webhook points at my Cloud Function, does the heavy lifting:</p>
<p><figure>
  <a href="#img-e7f613cfa16f7622a2820943f5ec5181">
    <img src="/img/pi-day/pi-ask-digit-intent.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e7f613cfa16f7622a2820943f5ec5181">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pi-day/pi-ask-digit-intent.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You can try it in the emulator:</p>
<p><figure>
  <a href="#img-d964f245abee27a4d6b7cbdb7adfa33e">
    <img src="/img/pi-day/pi-simulator.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d964f245abee27a4d6b7cbdb7adfa33e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/pi-day/pi-simulator.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>After that, once the webhook is properly configured, I published my action, through the integrations pane, and the cloud API console. I&rsquo;ll skip the details here, but you can read more on <a href="https://developers.google.com/actions/distribute/">how to distribution your actions</a>.</p>
<p>So again, Happy Pi Day! And hopefully, if you have a Google Home device and when my assistant is officially published, you&rsquo;ll be able to learn more about the digits of Pi!</p>
<p>And let&rsquo;s finish with a video of the assistant running live on my Google Home!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/JrqdhpSmNRc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Extending the Google Assistant with Actions on Google</title><link>https://glaforge.dev/talks/2017/03/13/extending-the-google-assistant-with-actions-on-google/</link><pubDate>Mon, 13 Mar 2017 15:21:06 +0100</pubDate><guid>https://glaforge.dev/talks/2017/03/13/extending-the-google-assistant-with-actions-on-google/</guid><description>&lt;p>Last week, in San Francisco, took place the &lt;a href="https://cloudnext.withgoogle.com/">Google Cloud Next 2017&lt;/a> conference, and I had the pleasure to co-present a session on &amp;ldquo;Extending the Google Assistant with Actions on Google&amp;rdquo;, with Brad Abrams, product manager on the assistant technology at Google.&lt;/p>
&lt;blockquote>
&lt;p>The Google Assistant is the conversational user interface that helps you get things done in your world. Actions on Google let you build on this assistance, while your integrations can help you engage users through Google Home on Pixel, Android and many other devices that connect with Google Assistant. In this session, we&amp;rsquo;ll share the latest innovations behind the Google Assistant and how you can leverage those technologies and best practices for Voice User Interface design to build your own custom extensions to Google Assistant.&lt;/p></description><content:encoded>
<![CDATA[<p>Last week, in San Francisco, took place the <a href="https://cloudnext.withgoogle.com/">Google Cloud Next 2017</a> conference, and I had the pleasure to co-present a session on &ldquo;Extending the Google Assistant with Actions on Google&rdquo;, with Brad Abrams, product manager on the assistant technology at Google.</p>
<blockquote>
<p>The Google Assistant is the conversational user interface that helps you get things done in your world. Actions on Google let you build on this assistance, while your integrations can help you engage users through Google Home on Pixel, Android and many other devices that connect with Google Assistant. In this session, we&rsquo;ll share the latest innovations behind the Google Assistant and how you can leverage those technologies and best practices for Voice User Interface design to build your own custom extensions to Google Assistant.</p></blockquote>
<p>In this presentation, for our demonstration, we used <a href="https://api.ai/">API.AI</a> and <a href="https://cloud.google.com/functions/">Google Cloud Functions</a> (announced as beta during the keynote) to implement our assistant, whose job was to help attendees learn more about the conference schedule and see which talks they&rsquo;d be interested in attending.</p>
<p>You can watch the video of the talk on YouTube already:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/7e0RGIul8Kk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And you can have a closer look at the slides below:</p>
<script async class="speakerdeck-embed" data-id="87cda359a4ba4718b789286c1be27379" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Google Cloud Endpoints in General Availability</title><link>https://glaforge.dev/posts/2017/02/13/google-cloud-endpoints-in-general-availability/</link><pubDate>Mon, 13 Feb 2017 15:30:57 +0100</pubDate><guid>https://glaforge.dev/posts/2017/02/13/google-cloud-endpoints-in-general-availability/</guid><description>&lt;p>Today was &lt;a href="http://cloudplatform.googleblog.com/2017/02/Google-Cloud-Endpoints-now-GA-a-fast-scalable-API-gateway.html">announced the general availability&lt;/a> of &lt;a href="https://cloud.google.com/endpoints/">Google Cloud Endpoints&lt;/a>!&lt;/p>
&lt;p>Endpoints is the Google Cloud Platform solution for Web API management, which lets you easily protect &amp;amp; secure your API, monitor it, without overhead, and even allows you to implement your API with any language or framework you want.&lt;/p>
&lt;p>I&amp;rsquo;ve spoken about Endpoints a few times already, at &lt;a href="https://glaforge.dev/talks/2016/11/15/binge-streaming-web-apis-with-ratpack-cloud-ednpoints-app-engine-flex-and-streamdata-io/">Devoxx Belgium&lt;/a>, &lt;a href="https://glaforge.dev/talks/2016/10/27/scaling-a-swagger-based-web-api-on-google-cloud-endpoints/">Nordic APIs summit&lt;/a>, and APIDays Paris. And you can see the recording of my Nordic APIs appearance, if you want to learn more about Cloud Endpoints:&lt;/p></description><content:encoded>
<![CDATA[<p>Today was <a href="http://cloudplatform.googleblog.com/2017/02/Google-Cloud-Endpoints-now-GA-a-fast-scalable-API-gateway.html">announced the general availability</a> of <a href="https://cloud.google.com/endpoints/">Google Cloud Endpoints</a>!</p>
<p>Endpoints is the Google Cloud Platform solution for Web API management, which lets you easily protect &amp; secure your API, monitor it, without overhead, and even allows you to implement your API with any language or framework you want.</p>
<p>I&rsquo;ve spoken about Endpoints a few times already, at <a href="https://glaforge.dev/talks/2016/11/15/binge-streaming-web-apis-with-ratpack-cloud-ednpoints-app-engine-flex-and-streamdata-io/">Devoxx Belgium</a>, <a href="https://glaforge.dev/talks/2016/10/27/scaling-a-swagger-based-web-api-on-google-cloud-endpoints/">Nordic APIs summit</a>, and APIDays Paris. And you can see the recording of my Nordic APIs appearance, if you want to learn more about Cloud Endpoints:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/M1VC5jT-mSU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A tight development loop for developing bots with API.ai, the Google Cloud Functions emulator, Node.js and Ngrok</title><link>https://glaforge.dev/posts/2017/02/05/a-tight-development-loop-for-developing-bots-with-apiai-the-google-cloud-functions-emulator-nodejs-and-ngrok/</link><pubDate>Sun, 05 Feb 2017 15:33:09 +0100</pubDate><guid>https://glaforge.dev/posts/2017/02/05/a-tight-development-loop-for-developing-bots-with-apiai-the-google-cloud-functions-emulator-nodejs-and-ngrok/</guid><description>&lt;p>For &lt;a href="https://cloudnext.withgoogle.com/">Google Cloud Next&lt;/a> and &lt;a href="http://devoxx.fr/">Devoxx France&lt;/a>, I&amp;rsquo;m working on a new talk showing how to build a conference assistant, to whom you&amp;rsquo;ll be able to ask questions like &amp;ldquo;what is the next talk about Java&amp;rdquo;, &amp;ldquo;when is Guillaume Laforge speaking&amp;rdquo;, &amp;ldquo;what is the topic of the ongoing keynote&amp;rdquo;, etc.&lt;/p>
&lt;p>For that purpose, I&amp;rsquo;m developing the assistant using &lt;a href="https://api.ai/">API.AI&lt;/a>. It&amp;rsquo;s a &amp;ldquo;conversational user experience platform&amp;rdquo; recently acquired by Google, which allows you to define various &amp;ldquo;intents&amp;rdquo; which correspond to the kind of questions / sentences that a user can say, and various &amp;ldquo;entities&amp;rdquo; which relate to the concepts dealt with (in my example, I have entities like &amp;ldquo;talk&amp;rdquo; or &amp;ldquo;speaker&amp;rdquo;). API.AI lets you define sentences pretty much in free form, and it derives what must be the various entities in the sentences, and is able to actually understand more sentences that you&amp;rsquo;ve given it. Pretty clever machine learning and natural language process at play. In addition to that, you also have support for several spoken languages (English, French, Italian, Chinese and more), integrations with key messaging platforms like Slack, Facebook Messenger, Twilio, or &lt;a href="https://madeby.google.com/home/">Google Home&lt;/a>. It also offers various SDKs so you can integrate it easily in your website, mobile application, backend code (Java, Android, Node, C#&amp;hellip;)&lt;/p></description><content:encoded>
<![CDATA[<p>For <a href="https://cloudnext.withgoogle.com/">Google Cloud Next</a> and <a href="http://devoxx.fr/">Devoxx France</a>, I&rsquo;m working on a new talk showing how to build a conference assistant, to whom you&rsquo;ll be able to ask questions like &ldquo;what is the next talk about Java&rdquo;, &ldquo;when is Guillaume Laforge speaking&rdquo;, &ldquo;what is the topic of the ongoing keynote&rdquo;, etc.</p>
<p>For that purpose, I&rsquo;m developing the assistant using <a href="https://api.ai/">API.AI</a>. It&rsquo;s a &ldquo;conversational user experience platform&rdquo; recently acquired by Google, which allows you to define various &ldquo;intents&rdquo; which correspond to the kind of questions / sentences that a user can say, and various &ldquo;entities&rdquo; which relate to the concepts dealt with (in my example, I have entities like &ldquo;talk&rdquo; or &ldquo;speaker&rdquo;). API.AI lets you define sentences pretty much in free form, and it derives what must be the various entities in the sentences, and is able to actually understand more sentences that you&rsquo;ve given it. Pretty clever machine learning and natural language process at play. In addition to that, you also have support for several spoken languages (English, French, Italian, Chinese and more), integrations with key messaging platforms like Slack, Facebook Messenger, Twilio, or <a href="https://madeby.google.com/home/">Google Home</a>. It also offers various SDKs so you can integrate it easily in your website, mobile application, backend code (Java, Android, Node, C#&hellip;)</p>
<p>When implementing your assistant, you&rsquo;ll need to implement some business logic. You need to retrieve the list of speakers, the list of talks from a backend or REST API. You also need to translate the search for a talk on a given topic into the proper query to that backend. In order to implement such logic, API.AI offers a Webhook interface. You instruct API.AI to point at your own URL that will take care of dealing with the request, and will reply adequately with the right data. To facilitate the development, you can take advantage of the SDKs I mentioned above, or you can also just parse and produce the right JSON payloads. To implement my logic, I decided to use <a href="https://cloud.google.com/functions/">Google Cloud Functions</a>, Google&rsquo;s recent serverless, function-based offering. Cloud Functions is currently is alpha, and supports JavaScript through Node.js.</p>
<p>For brevity sake, I&rsquo;ll focus on a simple example today. I&rsquo;m going to create a small agent that replies to queries like &ldquo;what time is it in Paris&rdquo; or some other city.<br />
In API.AI, we&rsquo;re going to create an &ldquo;city&rdquo; entity with a few city names:</p>
<p><figure>
  <a href="#img-6939ac0c0cfd57998579439d4a090280">
    <img src="/img/tightloop/wtii-01-entity.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6939ac0c0cfd57998579439d4a090280">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-01-entity.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Next, we&rsquo;re creating the &ldquo;ask-for-the-time&rdquo; intent, with a sentence like &ldquo;what time it is in Paris?&rdquo;:</p>
<p><figure>
  <a href="#img-3c81141f90352e1ce616315165ecdd90">
    <img src="/img/tightloop/wtii-02-intent.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3c81141f90352e1ce616315165ecdd90">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-02-intent.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Quick remark, when creating my intent, I didn&rsquo;t use the built-in @sys.geo-city data type, I just created my own city kind, but I was pleasantly surprised that it recognized the city name as a potential @sys.geo-city type. Neat!<br />
With our intent and entity ready, we enable the &ldquo;fulfillment&rdquo;, so that API.AI knows it should call our own business logic for replying to that query:</p>
<p><figure>
  <a href="#img-90d17af69b4adf81d2a7458757dd912c">
    <img src="/img/tightloop/wtii-03-fullfillment.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-90d17af69b4adf81d2a7458757dd912c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-03-fullfillment.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And that&rsquo;s in the URL field that we&rsquo;ll be able to point at our business logic developed as a Cloud Function. But first, we&rsquo;ll need to implement our function.</p>
<p>After having created a project in the Google Cloud console (you might need to request being whitelisted, as at the time of this writing the product is still in alpha), I create a new function, that I&rsquo;m simply calling &lsquo;agent&rsquo;. I define the function as being triggered by an HTTP call, and with the source code inline.</p>
<p>For the source of my function, I&rsquo;m using the &ldquo;actions-on-google&rdquo; NPM module, that I&rsquo;m defining in the package.json file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;what-time-is-it&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;version&#34;</span>: <span style="color:#4070a0">&#34;0.0.1&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;private&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;scripts&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;start&#34;</span>: <span style="color:#4070a0">&#34;node server.js&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;deploy&#34;</span>: <span style="color:#4070a0">&#34;gcloud alpha functions deploy agent --project what-time-is-it-157614  --trigger-http --stage-bucket gs://what-time-is-it-157614/&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;description&#34;</span>: <span style="color:#4070a0">&#34;An agent to know the time in various cities around the world.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;main&#34;</span>: <span style="color:#4070a0">&#34;index.js&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;repository&#34;</span>: <span style="color:#4070a0">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;author&#34;</span>: <span style="color:#4070a0">&#34;Guillaume Laforge&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;dependencies&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;actions-on-google&#34;</span>: <span style="color:#4070a0">&#34;^1.0.5&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And the implementation looks like the following:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span> ApiAiAssistant <span style="color:#666">=</span> require(<span style="color:#4070a0">&#39;actions-on-google&#39;</span>).ApiAiAssistant;
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> ASK_TIME_INTENT <span style="color:#666">=</span> <span style="color:#4070a0">&#39;ask-for-the-time&#39;</span>;  
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">const</span> CITY <span style="color:#666">=</span> <span style="color:#4070a0">&#39;city&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">function</span> whatTimeIsIt(assistant) {
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">var</span> city <span style="color:#666">=</span> assistant.getArgument(CITY);
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">if</span> (city <span style="color:#666">===</span> <span style="color:#4070a0">&#39;Paris&#39;</span>) 
</span></span><span style="display:flex;"><span>    assistant.ask(<span style="color:#4070a0">&#34;It&#39;s noon in Paris.&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">else</span> <span style="color:#007020;font-weight:bold">if</span> (city <span style="color:#666">===</span> <span style="color:#4070a0">&#39;London&#39;</span>) 
</span></span><span style="display:flex;"><span>    assistant.ask(<span style="color:#4070a0">&#34;It&#39;s 11 a.m. in London.&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#007020;font-weight:bold">else</span> 
</span></span><span style="display:flex;"><span>    assistant.ask(<span style="color:#4070a0">&#34;It&#39;s way to early or way too late in &#34;</span> <span style="color:#666">+</span> city);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>exports.agent <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">function</span>(request, response) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">var</span> assistant <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> ApiAiAssistant({request<span style="color:#666">:</span> request, response<span style="color:#666">:</span> response});
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">var</span> actionMap <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> Map();
</span></span><span style="display:flex;"><span>    actionMap.set(ASK_TIME_INTENT, whatTimeIsIt);
</span></span><span style="display:flex;"><span>    assistant.handleRequest(actionMap);
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Once my function is created, after 30 seconds or so, the function is actually deployed and ready to serve its first requests. I update the fulfillment details to point at the URL of my newly created cloud function. Then I can use the API.AI console to make a first call to my agent:</p>
<p><figure>
  <a href="#img-bcb676d523ede915760959dbfa96bb2b">
    <img src="/img/tightloop/wtii-04-testing.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-bcb676d523ede915760959dbfa96bb2b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-04-testing.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You can see that my function replied it was noon in Paris. When clicking the &ldquo;SHOW JSON&rdquo; button, you can also see the JSON being exchanged:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;id&#34;</span>: <span style="color:#4070a0">&#34;20ef54be-ee01-4fbe-9e6e-e73305046601&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;timestamp&#34;</span>: <span style="color:#4070a0">&#34;2017-02-03T22:22:08.822Z&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;result&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;source&#34;</span>: <span style="color:#4070a0">&#34;agent&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;resolvedQuery&#34;</span>: <span style="color:#4070a0">&#34;what time is it in paris?&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;action&#34;</span>: <span style="color:#4070a0">&#34;ask-for-the-time&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;actionIncomplete&#34;</span>: <span style="color:#007020;font-weight:bold">false</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;parameters&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;city&#34;</span>: <span style="color:#4070a0">&#34;Paris&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;contexts&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;_actions_on_google_&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;parameters&#34;</span>: {
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;city&#34;</span>: <span style="color:#4070a0">&#34;Paris&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;city.original&#34;</span>: <span style="color:#4070a0">&#34;Paris&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;lifespan&#34;</span>: <span style="color:#40a070">100</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;metadata&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;intentId&#34;</span>: <span style="color:#4070a0">&#34;b98aaae0-838a-4d55-9c8d-6adef4a4d798&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;webhookUsed&#34;</span>: <span style="color:#4070a0">&#34;true&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;webhookForSlotFillingUsed&#34;</span>: <span style="color:#4070a0">&#34;true&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;intentName&#34;</span>: <span style="color:#4070a0">&#34;ask-for-the-time&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;fulfillment&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;speech&#34;</span>: <span style="color:#4070a0">&#34;It&#39;s noon in Paris.&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;messages&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#40a070">0</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;speech&#34;</span>: <span style="color:#4070a0">&#34;It&#39;s noon in Paris.&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;google&#34;</span>: {
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;expect_user_response&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;is_ssml&#34;</span>: <span style="color:#007020;font-weight:bold">false</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;no_input_prompts&#34;</span>: []
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;score&#34;</span>: <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;status&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;code&#34;</span>: <span style="color:#40a070">200</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;errorType&#34;</span>: <span style="color:#4070a0">&#34;success&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;sessionId&#34;</span>: <span style="color:#4070a0">&#34;4ba74fa2-e462-4992-9587-2439b32aad3d&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>So far so good, it worked. But as you start fleshing out your agent, you&rsquo;re going to continue making tests manually, then update your code and redeploy the function, several times. Although the deployment times of Cloud Function is pretty fast (30 seconds or so), as you make even simple tweaks to your function&rsquo;s source code, adding several times 30 seconds, you will quickly feel like you&rsquo;re wasting a bit of time waiting for those deployments. What if&hellip; you could run your function locally on your machine, let API.AI point at your local machine somehow through its fulfillment configuration, and make changes live to your code, and test the changes right away without needing any redeployment! We can! We are going to do so by using the Cloud Functions emulator, as well as the very nice ngrok tool which allows you to expose your local host to the internet. Let&rsquo;s install the Cloud Functions emulator, as shown in its documentation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install -g @google-cloud/functions-emulator
</span></span></code></pre></div><p>Earlier, we entered the code of our function (index.js and package.json) directly in the Google Cloud Platform web console, but we will now retrieve them locally, to run them from our own machine. We will also need to install the actions-on-google npm module for our project to run:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install actions-on-google
</span></span></code></pre></div><p>Once the emulator is installed (you&rsquo;ll need at least Node version 6.9), you can define your project ID with something like the following (update to your actual project ID):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>functions config <span style="color:#007020">set</span> projectId what-time-is-it-157614
</span></span></code></pre></div><p>And then we can start the emulator, as a daemon, with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>functions start
</span></span></code></pre></div><p>We deploy the function locally with the command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>functions deploy agent --trigger-http
</span></span></code></pre></div><p>If the function deployed successfully on your machine, you should see the following:</p>
<p><figure>
  <a href="#img-bcdb57e419a58a4299221ec0eb989871">
    <img src="/img/tightloop/witi-05-function-deployed.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-bcdb57e419a58a4299221ec0eb989871">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/witi-05-function-deployed.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Notice that your function is running on localhost at:</p>
<pre tabindex="0"><code>http://localhost:8010/what-time-is-it-157614/us-central1/agent
</code></pre><p>We want this function to be accessible from the web. That&rsquo;s where our ngrok magic bullet will help us. Once you&rsquo;ve signed-up to the service and installed it on your machine, you can run ngrok with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ngrok http <span style="color:#40a070">8010</span>
</span></span></code></pre></div><p>The command will expose your service on the web, and allow you to have a public, accessible https endpoint:</p>
<p><figure>
  <a href="#img-1dc1fc77165bbc32b354f3cb124f1cc1">
    <img src="/img/tightloop/wtii-06-ngrok.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1dc1fc77165bbc32b354f3cb124f1cc1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-06-ngrok.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In the API.AI interface, you must update the fulfillment webhook endpoint to point to that https URL: <a href="https://acc0889e.ngrok.io/">https://acc0889e.ngrok.io</a>. But you must also append the path shown when running on localhost: what-time-is-it-157614/us-central1/agent, so the full path to indicate in the fulfillment URL will be: <a href="https://acc0889e.ngrok.io/what-time-is-it-157614/us-central1/agent">https://acc0889e.ngrok.io/what-time-is-it-157614/us-central1/agent</a></p>
<p><figure>
  <a href="#img-8c04ad65986783ab4258b8bbcd87e8a3">
    <img src="/img/tightloop/wtii-07-new-fulfillment.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8c04ad65986783ab4258b8bbcd87e8a3">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-07-new-fulfillment.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then I use the API.AI console to send another test request, for instance what is the time in San Francisco. And it&rsquo;s calling my local function:</p>
<p><figure>
  <a href="#img-89f7e1b6555c330ecc4150d7c6c8412d">
    <img src="/img/tightloop/wtii-08-typo.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-89f7e1b6555c330ecc4150d7c6c8412d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-08-typo.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>And in the ngrok local console, you can indeed see that it&rsquo;s my local function that has been called in the emulator:</p>
<p><figure>
  <a href="#img-027e4e592815f9526cb817af3dde0776">
    <img src="/img/tightloop/wtii-09-ngrok.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-027e4e592815f9526cb817af3dde0776">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-09-ngrok.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Nice, it worked! We used the Cloud Functions emulator, in combination with ngrok, to route fulfillment request to our local machine. However, the astute reader might have noticed that my bot&rsquo;s answer contained a typo, I wrote &ldquo;to early&rdquo;, instead of &ldquo;too early&rdquo;. Damn! I&rsquo;ll need to fix that locally, in a tight feedback loop, rather than having to redeploy all the time my function. How do I go about it? I just open my IDE or text editor, fix the typo, and here you go, nothing to redeploy locally or anything, the change is already applied and live. If I make a call in the API.AI console, the typo is fixed:</p>
<p><figure>
  <a href="#img-955ec21997e7753f6350f886407dbf7b">
    <img src="/img/tightloop/wtii-10-typo-fixed.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-955ec21997e7753f6350f886407dbf7b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/tightloop/wtii-10-typo-fixed.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Thanks to the <a href="https://github.com/GoogleCloudPlatform/cloud-functions-emulator">Cloud Functions emulator</a> and <a href="https://ngrok.com/">ngrok</a>, I can develop locally on my machine, with a tight develop / test loop, without having to deploy my functions all the time. The changes are taken into account live: no need to restart the emulator, or deploy the function locally. Once I&rsquo;m happy with the result, I can deploy for real. Then, I&rsquo;ll have to remember to change the webhook fulfillment URL to the real live cloud function.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>My favorite Cloud Next sessions</title><link>https://glaforge.dev/posts/2017/01/20/my-favorite-cloud-next-sessions/</link><pubDate>Fri, 20 Jan 2017 15:38:37 +0100</pubDate><guid>https://glaforge.dev/posts/2017/01/20/my-favorite-cloud-next-sessions/</guid><description>&lt;p>The &lt;a href="https://cloudnext.withgoogle.com/schedule">schedule&lt;/a> for Google &lt;a href="https://cloudnext.withgoogle.com/">Cloud Next&lt;/a> was unveiled this week, and there&amp;rsquo;s lots of interesting sessions to attend. With the many parallel tracks, it&amp;rsquo;s difficult to make a choice, but I wanted to highlight some of the talks I&amp;rsquo;d like to watch!&lt;/p>
&lt;p>The Google Cloud Platform is a pretty rich one, with many options for your compute needs. How do you choose which one is best for your use case? Brian Dorsey covers this in detail in this session:&lt;/p></description><content:encoded>
<![CDATA[<p>The <a href="https://cloudnext.withgoogle.com/schedule">schedule</a> for Google <a href="https://cloudnext.withgoogle.com/">Cloud Next</a> was unveiled this week, and there&rsquo;s lots of interesting sessions to attend. With the many parallel tracks, it&rsquo;s difficult to make a choice, but I wanted to highlight some of the talks I&rsquo;d like to watch!</p>
<p>The Google Cloud Platform is a pretty rich one, with many options for your compute needs. How do you choose which one is best for your use case? Brian Dorsey covers this in detail in this session:</p>
<p><a href="https://cloudnext.withgoogle.com/schedule#target=where-should-i-run-my-code-deciding-between-compute-engine-container-engine-app-engine-and-more-91e716a3-813e-43c9-a513-27d3365a449b">&ldquo;Where should I run my code?&rdquo; Deciding between Compute Engine, Container Engine, App Engine and more</a></p>
<p>To explore a bit further some of the compute options, I&rsquo;d recommend looking at Container Engine with <a href="https://cloudnext.withgoogle.com/schedule#target=abcs-of-google-container-engine-tips-and-best-practices-03ed20df-b6c9-4e0a-911f-b33017f53477">ABCs of Google Container Engine: tips and best practices</a> by Piotr Szczesniak, and <a href="https://cloudnext.withgoogle.com/schedule#target=go-beyond-paas-with-app-engine-flexible-environment-89abaf38-fce4-451c-b0e3-7726013300df">Go beyond PaaS with App Engine Flexible Environment</a> by Justin Beckwith.</p>
<p>The Serverless trend is strong these days, and in this area, I spotted two slots here with Firebase, Cloud Functions: <a href="https://cloudnext.withgoogle.com/schedule#target=live-coding-a-serverless-app-with-firebase-and-google-cloud-platform-d9026f62-4bfe-4087-b2d8-ffacd52222a2">Live coding a serverless app with Firebase and Google Cloud Platform</a> by Mike McDonald, Jen Tong, Frank van Puffelen, and <a href="https://cloudnext.withgoogle.com/schedule#target=serverless-computing-options-with-google-cloud-platform-1beab0c1-740e-4ee5-82f7-2fc020cdb295">Serverless computing options with Google Cloud Platform</a> by Bret McGowen.</p>
<p>I&rsquo;ve blogged before about Cloud Endpoints, as I&rsquo;m interested in the world of <a href="https://glaforge.dev/tags/web-apis/">Web APIs</a>, and there are two talks I&rsquo;d like to attend in this area: <a href="https://cloudnext.withgoogle.com/schedule#target=google-cloud-endpoints-serving-your-api-to-the-world-8eaeb271-f0ea-4638-af94-16c0b2b80bf6">Google Cloud Endpoints: serving your API to the world</a> by Francesc Campoy Flores and <a href="https://cloudnext.withgoogle.com/schedule#target=authorizing-service-to-service-calls-with-google-cloud-endpoints-24f2852c-e586-4fe9-8eb6-0e13e8585800">Authorizing service-to-service calls with Google Cloud Endpoints</a> by Dan Ciruli, Sep Ebrahimzadeh.</p>
<p>And in my misc. category, I&rsquo;d like to highlight this one on the APIs for G Suite: <a href="https://cloudnext.withgoogle.com/schedule#target=developing-new-apps-built-for-your-organization-with-google-docs-slides-sheets-and-sites-apis-bc17ab71-ce12-4057-bcc6-7fe536f67490">Developing new apps built for your organization with Google Docs, Slides, Sheets and Sites APIs</a> by Ritcha Ranjan. A talk on big parallel data processing with <a href="https://cloudnext.withgoogle.com/schedule#target=using-apache-beam-for-parallel-data-processing-a7a06ee0-7b93-4559-82e0-1a8dfd515771">Using Apache Beam for parallel data processing</a> by Frances Perry.</p>
<p>And to finish, I have to mention my own talk, that I&rsquo;ll be presenting with Brad Abrams: <a href="https://cloudnext.withgoogle.com/schedule#target=talking-to-your-users-build-conversational-actions-for-google-assistant-5a780cf1-4cc2-4ce4-824d-4ff7b7c7a14c">Talking to your users: Build conversational actions for Google Assistant</a>. It should be fun!</p>
<p>What talks are you going to attend?</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Deploy a Ratpack App on Google App Engine Flex</title><link>https://glaforge.dev/posts/2017/01/15/deploy-a-ratpack-app-on-google-app-engine-flex/</link><pubDate>Sun, 15 Jan 2017 15:40:27 +0100</pubDate><guid>https://glaforge.dev/posts/2017/01/15/deploy-a-ratpack-app-on-google-app-engine-flex/</guid><description>&lt;p>The purpose of this article is to deploy a &lt;a href="https://ratpack.io/">Ratpack&lt;/a> web application on &lt;a href="https://cloud.google.com/appengine/docs/flexible/">Google App Engine Flex&lt;/a>.&lt;/p>
&lt;p>For my demos at conferences, I often use frameworks like &lt;a href="https://ratpack.io/">Ratpack&lt;/a>, &lt;a href="https://grails.org/">Grails&lt;/a> or &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a>, which are based on the &lt;a href="http://www.groovy-lang.org/">Apache Groovy&lt;/a> programming language. In a &lt;a href="https://glaforge.dev/talks/2016/11/15/binge-streaming-web-apis-with-ratpack-cloud-ednpoints-app-engine-flex-and-streamdata-io/">previous article&lt;/a>, I already used Ratpack, but on a slightly more complex use case, but this time, I want to share a quick Ratpack hello world, and deploy it on &lt;a href="https://cloud.google.com/appengine/docs/flexible/">Flex&lt;/a>.&lt;/p>
&lt;p>I started with a hello world template generated by &lt;a href="https://github.com/pledbrook/lazybones">Lazybones&lt;/a> (a simple project creation tool that uses packaged project templates), that I had installed with &lt;a href="http://sdkman.io/">SDKman&lt;/a> (a tool for managing parallel versions of multiple Software Development Kits). But you can go ahead with your own Ratpack apps obviously. Feel free to skip the next section if you already have an app.&lt;/p></description><content:encoded>
<![CDATA[<p>The purpose of this article is to deploy a <a href="https://ratpack.io/">Ratpack</a> web application on <a href="https://cloud.google.com/appengine/docs/flexible/">Google App Engine Flex</a>.</p>
<p>For my demos at conferences, I often use frameworks like <a href="https://ratpack.io/">Ratpack</a>, <a href="https://grails.org/">Grails</a> or <a href="http://gaelyk.appspot.com/">Gaelyk</a>, which are based on the <a href="http://www.groovy-lang.org/">Apache Groovy</a> programming language. In a <a href="https://glaforge.dev/talks/2016/11/15/binge-streaming-web-apis-with-ratpack-cloud-ednpoints-app-engine-flex-and-streamdata-io/">previous article</a>, I already used Ratpack, but on a slightly more complex use case, but this time, I want to share a quick Ratpack hello world, and deploy it on <a href="https://cloud.google.com/appengine/docs/flexible/">Flex</a>.</p>
<p>I started with a hello world template generated by <a href="https://github.com/pledbrook/lazybones">Lazybones</a> (a simple project creation tool that uses packaged project templates), that I had installed with <a href="http://sdkman.io/">SDKman</a> (a tool for managing parallel versions of multiple Software Development Kits). But you can go ahead with your own Ratpack apps obviously. Feel free to skip the next section if you already have an app.</p>
<h2 id="create-a-ratpack-project">Create a Ratpack project</h2>
<pre tabindex="0"><code># install SDKman
curl -s &#34;https://get.sdkman.io&#34; | bash
# install lazybones with sdkman
sdk install lazybones
# create your hello world Ratpack app from a template
lazybones create ratpack flex-test-1
</code></pre><p>You can then quickly run your app with:</p>
<pre tabindex="0"><code>cd flex-test-1
./gradlew run
</code></pre><p>And head your browser to http://localhost:5050 to see your app running.</p>
<p>We&rsquo;ll use the distTar task to create a distribution of our app, so build it with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew distTar
</span></span></code></pre></div><h2 id="get-ready-for-flex">Get ready for Flex</h2>
<p>To run our app on App Engine Flex, we&rsquo;ll need to do two things: 1) to containerize it as a Docker container, and 2) to create an app.yaml app descriptor. Let&rsquo;s start with Docker. Create a Dockerfile, and adapt the path names appropriately (replace &ldquo;flex-test-1&rdquo; by the name of the directory you created your project in):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> gcr.io/google_appengine/openjdk8</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">VOLUME</span><span style="color:#4070a0"> /tmp</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ADD</span> build/distributions/flex-test-1.tar /<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ENV</span> <span style="color:#bb60d5">JAVA_OPTS</span><span style="color:#666">=</span><span style="color:#4070a0">&#39;-Dratpack.port=8080 -Djava.security.egd=file:/dev/./urandom&#39;</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ENTRYPOINT</span> [<span style="color:#4070a0">&#34;/flex-test-1/bin/flex-test-1&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>I&rsquo;m using Open JDK 8 for my <a href="https://cloud.google.com/appengine/docs/flexible/custom-runtimes/">custom runtime</a>. I add my tarred project, and specify port 8080 for running (as requested by Flex), and I define the entry point to my generated startup script.</p>
<p>My app.yaml file, for App Engine Flex, is pretty short, and expresses that I&rsquo;m using the Flexible environment:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">runtime</span>:<span style="color:#bbb"> </span>custom<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">env</span>:<span style="color:#bbb"> </span>flex<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">threadsafe</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="create-and-deploy-your-project-on-google-cloud-platform">Create and deploy your project on Google Cloud Platform</h2>
<p>Create an App Engine project on the <a href="https://console.cloud.google.com/appengine">Google Cloud Platform console</a>. And note the project name. You should also <a href="https://cloud.google.com/sdk/downloads">install the gcloud SDK</a> to be able to deploy your Ratpack app from the command-line. Once done, you&rsquo;ll be able to go through the deployment with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud app deploy
</span></span></code></pre></div><p>After a little while, your Ratpack should be up and running!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>New Features in the Google Cloud Natural Language Api Thanks to Your Feedback</title><link>https://glaforge.dev/posts/2017/01/03/new-features-in-the-google-cloud-natural-language-api-thanks-to-your-feedback/</link><pubDate>Tue, 03 Jan 2017 12:06:50 +0100</pubDate><guid>https://glaforge.dev/posts/2017/01/03/new-features-in-the-google-cloud-natural-language-api-thanks-to-your-feedback/</guid><description>&lt;p>&lt;strong>The GA release of Cloud Natural Language API is easier to use, better at recognizing language nuances and adds additional support for Spanish and Japanese&lt;/strong>&lt;/p>
&lt;p>Earlier in November, we announced &lt;a href="https://cloudplatform.googleblog.com/2016/11/Cloud-Machine-Learning-family-grows-with-new-API-editions-and-pricing.html">general availability for the Cloud Natural Language API&lt;/a> and highlighted the &lt;a href="https://cloud.google.com/blog/big-data/2016/11/the-evolution-of-cloud-natural-language-api">key new improvements&lt;/a>. This launch included many additions to the API like expanded entity recognition, granular sentiment analysis with expanded language support, improved syntax analysis with additional morphologies and more.&lt;/p>
&lt;p>Many of these improvements were the result of feedback from beta users, so thank you for your contributions! But concretely, what do these updates mean? Let&amp;rsquo;s take a closer look.&lt;/p></description><content:encoded>
<![CDATA[<p><strong>The GA release of Cloud Natural Language API is easier to use, better at recognizing language nuances and adds additional support for Spanish and Japanese</strong></p>
<p>Earlier in November, we announced <a href="https://cloudplatform.googleblog.com/2016/11/Cloud-Machine-Learning-family-grows-with-new-API-editions-and-pricing.html">general availability for the Cloud Natural Language API</a> and highlighted the <a href="https://cloud.google.com/blog/big-data/2016/11/the-evolution-of-cloud-natural-language-api">key new improvements</a>. This launch included many additions to the API like expanded entity recognition, granular sentiment analysis with expanded language support, improved syntax analysis with additional morphologies and more.</p>
<p>Many of these improvements were the result of feedback from beta users, so thank you for your contributions! But concretely, what do these updates mean? Let&rsquo;s take a closer look.</p>
<h2 id="granular-sentiment-analysis">Granular sentiment analysis</h2>
<p>When analyzing the <a href="http://glaforge.appspot.com/article/natural-language-api-and-javascript-promises-to-bind-them-all">sentiment of the White House speeches</a>, we were interested in the flow of phrases and how sentiment evolves throughout the text. We were looking at each sentence one at a time, making an API call for every sentence! This is obviously not the most efficient way to analyze a long document.</p>
<p>With this new version of the Cloud Natural Language API, you still have the overall sentiment of the article but you also get individual, per-sentence sentiment with a score (ranging between -1 and +1, from negative to positive) and a magnitude. In cases where a sentence or document&rsquo;s score is close to zero, it&rsquo;s helpful to look at magnitude to distinguish between neutral or mixed text. Text with both positive and negative sentiment will have a higher magnitude, whereas text with neutral sentiment will have a magnitude closer to zero. Unlike score, magnitude is not normalized and shows the total amount of sentiment in the text.</p>
<p>Let&rsquo;s look at a few sentences from Mrs. Obama&rsquo;s <a href="https://www.whitehouse.gov/the-press-office/2016/11/15/remarks-first-lady-national-arts-and-humanities-youth-program-awards">address</a> at the National Arts and Humanities Youth Program Awards:</p>
<h3 id="this-kind-of-work-is-hard-too-often-its-thankless-but-you-all-do-it-because-you-see-firsthand-the-transformative-impact-that-the-arts-can-have-on-our-young-people-and-were-grateful-to-you-all-for-doing-this-kind-of-work">This kind of work is hard. Too often it&rsquo;s thankless. But you all do it because you see firsthand the transformative impact that the arts can have on our young people. And we&rsquo;re grateful to you all for doing this kind of work.</h3>
<p>In addition to an overall score of 0.3 and magnitude of 1.7, we also get the sentiment for each sentence:</p>
<p><figure>
  <a href="#img-312e8ced159fabec0fda278de672029b">
    <img src="/img/nlp/new-features-NL-API-16utb.max-1000x1000.png"
      alt="/img/nlp/new-features-NL-API-16utb.max-1000x1000.png"
       />
  </a>
  <figcaption>/img/nlp/new-features-NL-API-16utb.max-1000x1000.png</figcaption>
</figure>
<div class="lightbox" id="img-312e8ced159fabec0fda278de672029b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nlp/new-features-NL-API-16utb.max-1000x1000.png"
    alt="/img/nlp/new-features-NL-API-16utb.max-1000x1000.png"
     />
  <div class="lightbox-caption">/img/nlp/new-features-NL-API-16utb.max-1000x1000.png</div>
</div>
</p>
<p>Notice how the first sentences establish a somewhat difficult context with neutral or slightly negative sentiment. The following sentences are much more positive, and with stronger magnitude. Sentiment analysis helps us understand how sentiment evolves throughout our document.</p>
<p>With fine-grained per-sentence, sentiment analysis, there&rsquo;s no need to fire multiple calls to the API &mdash; just one per document. A large document can have widely varying sentiment, and a more detailed analysis of the tone at a granular level provides more insight into the understanding of the text.</p>
<p>For a deep dive on using sentiment analysis on tweets, take a look at <a href="https://cloud.google.com/blog/big-data/2016/09/parsing-our">this blog post</a> about analyzing tweets during the 2016 Rio Olympics. Y Media Labs also published a <a href="https://www.ymedialabs.com/google-sentiment-analysis-api/">post</a> recently about using the Cloud Natural Language API to compare the sentiment of ridesharing apps.</p>
<h2 id="expanded-entity-recognition">Expanded entity recognition</h2>
<p>In a sentence like &ldquo;Adele is an English singer-songwriter,&rdquo; we naturally know that &ldquo;Adele&rdquo; and the &ldquo;English singer-songwriter&rdquo; are the one and only person. But it&rsquo;s not necessarily that trivial for computers. Let&rsquo;s see what the API tells us, when you <a href="https://cloud.google.com/natural-language/">try it online</a>:</p>
<p><figure>
  <a href="#img-5307baec9f328f35b8422f25310b98ac">
    <img src="/img/nlp/new-features-NL-API-2w377.max-800x800.png"
      alt="/img/nlp/new-features-NL-API-2w377.max-800x800.png"
       />
  </a>
  <figcaption>/img/nlp/new-features-NL-API-2w377.max-800x800.png</figcaption>
</figure>
<div class="lightbox" id="img-5307baec9f328f35b8422f25310b98ac">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nlp/new-features-NL-API-2w377.max-800x800.png"
    alt="/img/nlp/new-features-NL-API-2w377.max-800x800.png"
     />
  <div class="lightbox-caption">/img/nlp/new-features-NL-API-2w377.max-800x800.png</div>
</div>
</p>
<p>Not only does the API point you at the Wikipedia <a href="http://en.wikipedia.org/wiki/Adele">entry</a> about Adele, but it also managed to figure out that both the name and description were about the same person (i.e., entity #1 in red).</p>
<p>If you look at the JSON payload returned:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#4070a0">&#34;entities&#34;</span><span style="">:</span> [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;Adele&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;PERSON&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;metadata&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;mid&#34;</span>: <span style="color:#4070a0">&#34;/m/02z4b_8&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;wikipedia_url&#34;</span>: <span style="color:#4070a0">&#34;http://en.wikipedia.org/wiki/Adele&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;salience&#34;</span>: <span style="color:#40a070">0.86802435</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;mentions&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&#34;content&#34;</span>: <span style="color:#4070a0">&#34;Adele&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&#34;beginOffset&#34;</span>: <span style="color:#40a070">0</span>
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;PROPER&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&#34;content&#34;</span>: <span style="color:#4070a0">&#34;singer-songwriter&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#062873;font-weight:bold">&#34;beginOffset&#34;</span>: <span style="color:#40a070">20</span>
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;type&#34;</span>: <span style="color:#4070a0">&#34;COMMON&#34;</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>    }<span style="">...</span>
</span></span></code></pre></div><p>Only one entity is returned, but two mentions are given, including the offset where those two mentions appear. The first mention is a proper name, while the second one is just a common name, but still referring to Adele.</p>
<p>Understanding how the various elements of a sentence refer to others is a powerful capability that can now be practiced by software, not just by humans!</p>
<h2 id="improved-syntax-analysis">Improved syntax analysis</h2>
<p>Interested in getting into the nitty-gritty linguistic details of a piece of text? The Cloud Natural Language API&rsquo;s syntax-analysis response now includes much more syntactic data for each word. Let&rsquo;s take this sentence as an example:</p>
<p><figure>
  <a href="#img-c818ba9d4a3bec9b1b4b274a1d1a8149">
    <img src="/img/nlp/new-features-nl-api-5vanr.max-1100x1100.png"
      alt="/img/nlp/new-features-nl-api-5vanr.max-1100x1100.png"
       />
  </a>
  <figcaption>/img/nlp/new-features-nl-api-5vanr.max-1100x1100.png</figcaption>
</figure>
<div class="lightbox" id="img-c818ba9d4a3bec9b1b4b274a1d1a8149">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nlp/new-features-nl-api-5vanr.max-1100x1100.png"
    alt="/img/nlp/new-features-nl-api-5vanr.max-1100x1100.png"
     />
  <div class="lightbox-caption">/img/nlp/new-features-nl-api-5vanr.max-1100x1100.png</div>
</div>
</p>
<p>In addition to getting the dependency (the arrows in the image above) and part of speech tag for each word, the API returns all sorts of other linguistic info.</p>
<p>Here&rsquo;s the JSON response we get for the word &ldquo;We&rdquo; in the above sentence:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;text&#34;</span>: {
</span></span><span style="display:flex;"><span>       <span style="color:#062873;font-weight:bold">&#34;content&#34;</span>: <span style="color:#4070a0">&#34;We&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;beginOffset&#34;</span>: <span style="color:#40a070">0</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;partOfSpeech&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;tag&#34;</span>: <span style="color:#4070a0">&#34;PRON&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;case&#34;</span>: <span style="color:#4070a0">&#34;NOMINATIVE&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;number&#34;</span>: <span style="color:#4070a0">&#34;PLURAL&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;person&#34;</span>: <span style="color:#4070a0">&#34;FIRST&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="">...</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;dependencyEdge&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;headTokenIndex&#34;</span>: <span style="color:#40a070">2</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;label&#34;</span>: <span style="color:#4070a0">&#34;NSUBJ&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;lemma&#34;</span>: <span style="color:#4070a0">&#34;We&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s dive deeper into the new values. The token JSON response tells us whether the word is plural or not, indicated by the <code>number</code> value. <code>person</code> tells us that this word is written in the first person. <code>case</code> is a bit more complex, and reveals the function performed by nouns or pronouns in a sentence. &ldquo;We&rdquo; is nominative because it&rsquo;s the subject of the verb &ldquo;are.&rdquo; If the subjects and verbs in the sentence were reversed (&ldquo;They taught us&rdquo;), &ldquo;us&rdquo; would return a value of <code>ACCUSATIVE</code> for case.</p>
<p>The word &ldquo;are&rdquo; in the sentence above returns two more new values:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;mood&#34;</span>: <span style="color:#4070a0">&#34;INDICATIVE&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;tense&#34;</span>: <span style="color:#4070a0">&#34;PRESENT&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>mood</code>, not to be confused with sentiment, refers to the <em>modality</em> of the word or the way in which something is done. The indicative modality in this example shows that this is a statement of fact. Imperative modality, in contrast, describes direct commands. In the sentence &ldquo;Go learn the Natural Language API,&rdquo; the word &ldquo;Go&rdquo; is imperative. This <code>mood</code> value is useful if you&rsquo;re building a bot or automated tool, where you need to understand if something is a statement or command.</p>
<p>The word &ldquo;linguistics&rdquo; in our example also returns a number value of <code>SINGULAR</code>. Even though &ldquo;linguistics&rdquo; could be misinterpreted as a plural form of the word linguistic since it ends in &ldquo;s,&rdquo; the API is able to pick up contextual clues that &ldquo;linguistics&rdquo; here refers to the singular field of linguistics. Finally, the API is able to identify &ldquo;Natural Language API&rdquo; as a proper noun.</p>
<h2 id="improved-support-for-spanish-and-japanese">Improved support for Spanish and Japanese</h2>
<p>The updated API now supports sentiment and additional morphology analysis for Spanish and Japanese. Let&rsquo;s see a couple examples in these languages!</p>
<p>In his poem &ldquo;Canción otoñal&rdquo; from <em>Libro de Poemas</em>, the Spanish poet Federico García Lorca writes: <em>La luz me troncha las alas y el dolor de mi tristeza</em> (which translates to &ldquo;The light shatters my wings and the pain of my sadness.&rdquo;) If you look at the verb &ldquo;troncha&rdquo; (to shatter), the API gives some interesting details:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#4070a0">&#34;partOfSpeech&#34;</span><span style="">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;tag&#34;</span>:    <span style="color:#4070a0">&#34;VERB&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;aspect&#34;</span>: <span style="color:#4070a0">&#34;IMPERFECTIVE&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;mood&#34;</span>:   <span style="color:#4070a0">&#34;INDICATIVE&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;number&#34;</span>: <span style="color:#4070a0">&#34;SINGULAR&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;person&#34;</span>: <span style="color:#4070a0">&#34;THIRD&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;proper&#34;</span>: <span style="color:#4070a0">&#34;NOT_PROPER&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;tense&#34;</span>:  <span style="color:#4070a0">&#34;PRESENT&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;voice&#34;</span>:  <span style="color:#4070a0">&#34;ACTIVE&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The verb has an &ldquo;imperfective&rdquo; aspect, an &ldquo;indicative&rdquo; mood, is &ldquo;singular,&rdquo; at the &ldquo;third person&rdquo; and uses an &ldquo;active&rdquo; voice at the &ldquo;present&rdquo; tense. The API knows the Spanish grammar perhaps better than you can remember it!</p>
<p>The Cloud Natural Language API is just as happy to analyze your Japanese content. Let&rsquo;s look at this sentence, which means &ldquo;We are learning more about language through the NL API.&rdquo; What does its structure looks like?</p>
<p><figure>
  <a href="#img-cb8ad8543065ffe9d9fbabadb8b549a5">
    <img src="/img/nlp/new-features-NL-API-34u8u.max-800x800.png"
      alt="/img/nlp/new-features-NL-API-34u8u.max-800x800.png"
       />
  </a>
  <figcaption>/img/nlp/new-features-NL-API-34u8u.max-800x800.png</figcaption>
</figure>
<div class="lightbox" id="img-cb8ad8543065ffe9d9fbabadb8b549a5">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/nlp/new-features-NL-API-34u8u.max-800x800.png"
    alt="/img/nlp/new-features-NL-API-34u8u.max-800x800.png"
     />
  <div class="lightbox-caption">/img/nlp/new-features-NL-API-34u8u.max-800x800.png</div>
</div>
</p>
<h2 id="try-it-in-the-browser">Try it in the browser!</h2>
<p>The new Cloud Natural Language API features provide:</p>
<ul>
<li>Granular sentiment analysis: you get sentiment data for each sentence in your document so you don&rsquo;t need an API call for each sentence. This makes your code more streamlined without requiring costly round-trips to our servers.</li>
<li>Additional morphology details: the subtleties of the language are clearly depicted with more detailed parts of speech tags for each token.</li>
<li>Improved entity recognition: the API now recognizes multiple mentions of the same entity.</li>
<li>Multi-language support: the API added sentiment analysis for Spanish and Japanese along with new morphology details for both languages.</li>
</ul>
<p>Head over to the Google Cloud <a href="https://cloud.google.com/natural-language/#nl_demo_section">Natural Language API page</a> and try these enhancements to the API for yourself in your browser. You&rsquo;ll be able to look-up entities, discover the sentiment of the text and understand the fine-grained details of the structure of the text with hierarchical graphics. You&rsquo;ll notice in the syntax visualization that some words are blue; hover over these words for more morphology details.</p>
<h2 id="next-steps">Next steps</h2>
<p>Want to start using the Cloud Natural Language API in your own apps? Try these next steps:</p>
<ul>
<li>Follow the <a href="https://cloud.google.com/natural-language/docs/getting-started">quickstart guide</a></li>
<li>Let us know what you think in the comments</li>
<li>Use the <a href="http://stackoverflow.com/questions/tagged/google-cloud-nl">google-cloud-nl</a> tag on Stack Overflow</li>
<li>Learn more about <a href="https://cloud.google.com/products/machine-learning/">other Cloud APIs for machine learning</a></li>
<li>Get trained on Cloud ML at a Next &lsquo;17 <a href="https://cloudnext.withgoogle.com/bootcamps/cloud-ml">technical bootcamp</a> on March 6-7 in San Francisco</li>
</ul>
<p>We&rsquo;re excited to see what you build!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A poor-man assistant with speech recognition and natural language processing</title><link>https://glaforge.dev/posts/2016/12/16/a-poor-man-assistant-with-speech-recognition-and-natural-language-processing/</link><pubDate>Fri, 16 Dec 2016 15:43:35 +0100</pubDate><guid>https://glaforge.dev/posts/2016/12/16/a-poor-man-assistant-with-speech-recognition-and-natural-language-processing/</guid><description>&lt;p>All sorts of voice-powered assistants are available today, and chat bots are the new black! In order to illustrate how such tools are made, I decided to create my own little basic conference assistant, using Google&amp;rsquo;s &lt;a href="https://cloud.google.com/speech/">Cloud Speech API&lt;/a> and &lt;a href="https://cloud.google.com/natural-language/">Cloud Natural Language API&lt;/a>. This is a demo I actually created for the Devoxx 2016 keynote, when Stephan Janssen invited me on stage to speak about Machine Learning. And to make this demo more fun, I implemented it with a shell script, some curl calls, plus some other handy command-line tools.&lt;/p></description><content:encoded>
<![CDATA[<p>All sorts of voice-powered assistants are available today, and chat bots are the new black! In order to illustrate how such tools are made, I decided to create my own little basic conference assistant, using Google&rsquo;s <a href="https://cloud.google.com/speech/">Cloud Speech API</a> and <a href="https://cloud.google.com/natural-language/">Cloud Natural Language API</a>. This is a demo I actually created for the Devoxx 2016 keynote, when Stephan Janssen invited me on stage to speak about Machine Learning. And to make this demo more fun, I implemented it with a shell script, some curl calls, plus some other handy command-line tools.</p>
<p>So what is this &ldquo;conference assistant&rdquo; all about? Thanks for asking. The idea is to ask questions to this assistant about topics you&rsquo;d like to see during the conference. For example: &ldquo;Is there a talk about the Google Cloud Vision API?&rdquo;. You send that voice request to the Speech API, which gives you back the transcript of the question. You can then use the Natural Language API to process that text to extract the relevant topic in that question. Then you query the conference schedule to see if there&rsquo;s a talk matching the topic.</p>
<p>Let&rsquo;s see this <a href="https://asciinema.org/a/95040">demo into action</a>, before diving into the details.</p>
<p>So how did I create this little command-line conference assistant? Let&rsquo;s start with a quick diagram showing the whole process and its steps:</p>
<p><figure>
  <a href="#img-fadbf4f963088173f6eeb952406dc93d">
    <img src="/img/misc/assistant-diagram.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-fadbf4f963088173f6eeb952406dc93d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/assistant-diagram.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<ul>
<li>First, I record the audio using the <a href="http://sox.sourceforge.net/">sox</a> command-line tool.</li>
<li>The audio file is saved locally, and I upload it to Google Cloud Storage (GCS).</li>
<li>I then call the Speech API, pointing it at my recorded audio file in GCS, so that it returns the text it recognized from the audio.</li>
<li>I use the <a href="https://stedolan.github.io/jq/">jq</a> command line tool to extract the words from the returned JSON payload, and only the words I&rsquo;m interested in (basically what appears after the &ldquo;about&rdquo; part of my query, ie. &ldquo;a talk <em>about</em> machine learning&rdquo;)</li>
<li>Lastly, I&rsquo;m calling a <a href="https://cse.google.com/">custom search engine</a> that points at the conference website schedule, to find the relevant talks that match my search query.</li>
</ul>
<p>Let&rsquo;s have a look at the script in more details (this is the simplified script without all the shiny terminal colors and logging output). You should create a project in the Google Cloud Console, and note its project ID, as we&rsquo;ll reuse it for storing our audio file.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#007020"></span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># create an API key to access the Speech and NL APIs</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># https://support.google.com/cloud/answer/6158862?hl=en</span>
</span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">API_KEY</span><span style="color:#666">=</span>YOUR API KEY HERE
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># create a Google Custom Search and retrieve its id</span>
</span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">CS_ID</span><span style="color:#666">=</span>THE ID OF YOUR GOOGLE CUSTOM SEARCH
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># to use sox for recording audio, you can install it with:</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># brew install sox --with-lame --with-flac --with-libvorbis</span>
</span></span><span style="display:flex;"><span>sox  -d -r 16k -c <span style="color:#40a070">1</span> query.flac
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># once the recording is over, hit CTRL-C to stop</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># upload the audio file to Google Cloud Storage with the gsutil command</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># see the documentation for installing it, as well as the gcloud CLI</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># https://cloud.google.com/storage/docs/gsutil_install</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># https://cloud.google.com/sdk/docs/</span>
</span></span><span style="display:flex;"><span>gsutil copy -a public-read query.flac gs://devoxx-ml-demo.appspot.com/query.flac
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># call the Speech API with the template request saved in speech-request.json:</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># {</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  &#34;config&#34;: {</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#      &#34;encoding&#34;:&#34;FLAC&#34;,</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#      &#34;sample_rate&#34;: 16000,</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#      &#34;language_code&#34;: &#34;en-US&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  },</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  &#34;audio&#34;: {</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#      &#34;uri&#34;:&#34;gs://YOUR-PROJECT-ID-HERE.appspot.com/query.flac&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  }</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#}</span>
</span></span><span style="display:flex;"><span>curl -s -X POST -H <span style="color:#4070a0">&#34;Content-Type: application/json&#34;</span> --data-binary @speech-request.json <span style="color:#4070a0">&#34;https://speech.googleapis.com/v1beta1/speech:syncrecognize?key=</span><span style="color:#70a0d0">${</span><span style="color:#bb60d5">API_KEY</span><span style="color:#70a0d0">}</span><span style="color:#4070a0">&#34;</span> &gt; speech-output.json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># retrieve the text recognized by the Speech API</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># using the jq to just extract the text part</span>
</span></span><span style="display:flex;"><span>cat speech-output.json | jq -r .results<span style="color:#666">[</span>0<span style="color:#666">]</span>.alternatives<span style="color:#666">[</span>0<span style="color:#666">]</span>.transcript &gt; text.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># prepare a query for the Natural Language API</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># replacing the @TEXT@ place holder with the text we got from Speech API;</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># the JSON query template looks like this:</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># {</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  &#34;document&#34;: {</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#    &#34;type&#34;: &#34;PLAIN_TEXT&#34;,</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#    &#34;content&#34;: &#34;@TEXT@&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  },</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  &#34;features&#34;: {</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#    &#34;extractSyntax&#34;: true,</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#    &#34;extractEntities&#34;: false,</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#    &#34;extractDocumentSentiment&#34;: false</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#  }</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">#}</span>
</span></span><span style="display:flex;"><span>sed <span style="color:#4070a0">&#34;s/@TEXT@/`cat text.txt`/g&#34;</span> nl-request-template.json &gt; nl-request.json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># call the Natural Language API with our template</span>
</span></span><span style="display:flex;"><span>curl -s -X POST -H <span style="color:#4070a0">&#34;Content-Type: application/json&#34;</span> --data-binary @nl-request.json https://language.googleapis.com/v1beta1/documents:annotateText?key<span style="color:#666">=</span><span style="color:#70a0d0">${</span><span style="color:#bb60d5">API_KEY</span><span style="color:#70a0d0">}</span> &gt; nl-output.json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># retrieve all the analyzed words from the NL call results</span>
</span></span><span style="display:flex;"><span>cat nl-output.json | jq -r .tokens<span style="color:#666">[]</span>.lemma  &gt; lemmas.txt
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># only keep the words after the &#34;about&#34; word which refer to the topic searched for</span>
</span></span><span style="display:flex;"><span>sed -n <span style="color:#4070a0">&#39;/about/,$p&#39;</span> lemmas.txt | tail -n +2 &gt; keywords.txt
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># join the words together to pass them to the search engine</span>
</span></span><span style="display:flex;"><span>cat keywords.txt | tr <span style="color:#4070a0">&#39;\n&#39;</span> <span style="color:#4070a0">&#39;+&#39;</span> &gt; encoded-keywords.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># call the Google Custom Search engine, with the topic search query</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># and use jq again to filter only the title of the first search result</span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># (the page covering the talk usually comes first)</span>
</span></span><span style="display:flex;"><span>curl -s <span style="color:#4070a0">&#34;https://www.googleapis.com/customsearch/v1?key=</span><span style="color:#bb60d5">$API_KEY</span><span style="color:#4070a0">&amp;cx=</span><span style="color:#bb60d5">$CS_ID</span><span style="color:#4070a0">&amp;q=`cat encoded-keywords.txt`&#34;</span> | jq .items<span style="color:#666">[</span>0<span style="color:#666">]</span>.title
</span></span></code></pre></div><p>And voila, we have our conference assistant on the command-line! We combined the <a href="https://cloud.google.com/speech/">Speech API</a> to recognize the voice and extract the text corresponding to the query audio, we analyze this text with the <a href="https://cloud.google.com/nl">Natural Language API</a>, and we use a few handy command-line tools to do the glue.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Machine intelligence at Google scale, vision / speech APIs, Tensorflow, and Cloud Machine Learning</title><link>https://glaforge.dev/talks/2016/12/04/machine-intelligence-at-google-scale-vision-speech-api-tensorflow-and-cloud-machine-learning/</link><pubDate>Sun, 04 Dec 2016 15:49:55 +0100</pubDate><guid>https://glaforge.dev/talks/2016/12/04/machine-intelligence-at-google-scale-vision-speech-api-tensorflow-and-cloud-machine-learning/</guid><description>&lt;p>With my colleague &lt;a href="https://twitter.com/martin_gorner">Martin Görner&lt;/a>, at the &lt;a href="http://cfp.devoxx.be/2016/talk/HFW-0944/Machine_Intelligence_at_Google_Scale:_Vision%2FSpeech_API,_TensorFlow_and_Cloud_Machine_Learning">Devoxx&lt;/a> conference in Belgium last month, we gave a talk on Machine Learning, on the various APIs provided by Google Cloud, the TensorFlow Machine Learning Open Source project, the Cloud ML service. I didn&amp;rsquo;t get a chance to publish the slides, so it&amp;rsquo;s time I fix that!&lt;/p>
&lt;p>Machine Intelligence at Google Scale: Vision/Speech API, TensorFlow and Cloud Machine Learning&lt;/p>
&lt;p>The biggest challenge of Deep Learning technology is the scalability. As long as using single GPU server, you have to wait for hours or days to get the result of your work. This doesn&amp;rsquo;t scale for production service, so you need a Distributed Training on the cloud eventually. Google has been building infrastructure for training the large scale neural network on the cloud for years, and now started to share the technology with external developers. In this session, we will introduce new pre-trained ML services such as Cloud Vision API and Speech API that works without any training. Also, we will look how TensorFlow and Cloud Machine Learning will accelerate custom model training for 10x - 40x with Google&amp;rsquo;s distributed training infrastructure.&lt;/p></description><content:encoded>
<![CDATA[<p>With my colleague <a href="https://twitter.com/martin_gorner">Martin Görner</a>, at the <a href="http://cfp.devoxx.be/2016/talk/HFW-0944/Machine_Intelligence_at_Google_Scale:_Vision%2FSpeech_API,_TensorFlow_and_Cloud_Machine_Learning">Devoxx</a> conference in Belgium last month, we gave a talk on Machine Learning, on the various APIs provided by Google Cloud, the TensorFlow Machine Learning Open Source project, the Cloud ML service. I didn&rsquo;t get a chance to publish the slides, so it&rsquo;s time I fix that!</p>
<p>Machine Intelligence at Google Scale: Vision/Speech API, TensorFlow and Cloud Machine Learning</p>
<p>The biggest challenge of Deep Learning technology is the scalability. As long as using single GPU server, you have to wait for hours or days to get the result of your work. This doesn&rsquo;t scale for production service, so you need a Distributed Training on the cloud eventually. Google has been building infrastructure for training the large scale neural network on the cloud for years, and now started to share the technology with external developers. In this session, we will introduce new pre-trained ML services such as Cloud Vision API and Speech API that works without any training. Also, we will look how TensorFlow and Cloud Machine Learning will accelerate custom model training for 10x - 40x with Google&rsquo;s distributed training infrastructure.</p>
<p>The video is available on YouTube (in particular, don&rsquo;t miss the cool demos!):</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/zqWt8oI4gEw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And you can look at the slides here:</p>
<script async class="speakerdeck-embed" data-id="b0bf5d296e4941ca89fc78c2efb37561" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Analyzing half a million Gradle build files</title><link>https://glaforge.dev/posts/2016/12/03/analyzing-half-a-million-gradle-build-files/</link><pubDate>Sat, 03 Dec 2016 15:52:46 +0100</pubDate><guid>https://glaforge.dev/posts/2016/12/03/analyzing-half-a-million-gradle-build-files/</guid><description>&lt;p>Gradle is becoming the build automation solution of choice among developers, in particular in the Java ecosystem. With the Github archive published as a Google BigQuery dataset, it&amp;rsquo;s possible to analyze those build files, and see if we can learn something interesting about them!&lt;/p>
&lt;p>This week, I was at the G3 Summit conference, and &lt;a href="https://glaforge.dev/talks/2018/03/23/what-do-we-learn-from-millions-of-source-files-in-github/">presented&lt;/a> about this topic: I covered the Apache Groovy language, as per my previous &lt;a href="https://glaforge.dev/posts/2016/07/06/what-can-we-learn-from-million-lines-of-groovy-code-on-github/">article&lt;/a>, but I expanded my queries to also look at &lt;a href="https://grails.org/">Grails&lt;/a> applications, and &lt;a href="https://gradle.org/">Gradle&lt;/a> build files. So let&amp;rsquo;s see what the dataset tells us about Gradle!&lt;/p></description><content:encoded>
<![CDATA[<p>Gradle is becoming the build automation solution of choice among developers, in particular in the Java ecosystem. With the Github archive published as a Google BigQuery dataset, it&rsquo;s possible to analyze those build files, and see if we can learn something interesting about them!</p>
<p>This week, I was at the G3 Summit conference, and <a href="https://glaforge.dev/talks/2018/03/23/what-do-we-learn-from-millions-of-source-files-in-github/">presented</a> about this topic: I covered the Apache Groovy language, as per my previous <a href="https://glaforge.dev/posts/2016/07/06/what-can-we-learn-from-million-lines-of-groovy-code-on-github/">article</a>, but I expanded my queries to also look at <a href="https://grails.org/">Grails</a> applications, and <a href="https://gradle.org/">Gradle</a> build files. So let&rsquo;s see what the dataset tells us about Gradle!</p>
<h2 id="number-of-gradle-build-files-and-repositories">Number of Gradle build files and repositories</h2>
<p>Instead of going through the whole Github dataset, I&rsquo;m going to restrict the dataset by saving only the Gradle build files in my own, smaller, dataset:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">RIGHT</span>(path,<span style="color:#bbb"> </span><span style="color:#40a070">7</span>)<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;.gradle&#39;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>This query returns only the files whose extension is .gradle. I&rsquo;m saving the results in my [github.gradle_build_files] table.</p>
<p>But I also need the content of those files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.contents]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>id<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">IN</span><span style="color:#bbb"> </span>(<span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>id<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github.gradle_build_files])<span style="color:#bbb">
</span></span></span></code></pre></div><p>And I will save the content in the table [github.gradle_build_contents].</p>
<p>Let&rsquo;s start with a simple query to count the Gradle build files on Github:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.gradle_build_files]<span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 488,311 Gradle build files! Roughly half a million.</p>
<p>This is the number of Gradle files: note that a project can contain several build files, that a repository can contain several projects, but also that the Github dataset only provides data on repositories for which it could detect an Open Source license. So it gives an idea of the reach of Gradle, but doesn&rsquo;t necessarily give you the exact number of Gradle-based projects in the wild! (and obviously can&rsquo;t even account for the projects hosted internally and elsewhere)</p>
<p>Since a repository can contain several build files, let&rsquo;s have a look at the number of repositories containing Gradle build files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(repo_name)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>repos<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>repo_name<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.gradle_build_files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>repo_name<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 102,803 repositories with Gradle build files.</p>
<p>I was curious to see the distribution of the number of build files across projects. So I used the quantiles function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>QUANTILES(buildFilesCount,<span style="color:#bbb"> </span><span style="color:#40a070">101</span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>repo_name,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(repo_name)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>buildFilesCount<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.gradle_build_files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>repo_name<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>buildFilesCount<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">DESC</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span></code></pre></div><p>I used a small increment (one percent), as the data was skewed towards some repositories with a huge amount of Gradle build files: essentially repositories like the Udemy course on Gradle for Android, or an online book about Android development, as they had tons of small build files or variations of build files with incremental changes for explanation purpose.</p>
<ul>
<li>22% of the repositories had only 1 build file</li>
<li>85% of the repositories had up to 5 build files</li>
<li>95% of the repositories had less than 10 build files</li>
</ul>
<p>The repository with the biggest amount of build files had 1333 of them!</p>
<h2 id="gradle-vs-maven">Gradle vs Maven</h2>
<p>You might also be interested in comparing Gradle and Maven, as they are often put against each other in holy build wars. If you look at the number of pom.xml files on Github:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%pom.xml&#39;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>There are about 1,007,705 pom.xml files vs the 488,311 we counted for Gradle. So roughly twice as many for Maven.</p>
<p>But if you look at the number of repositories with Maven build files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(repo_name)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>repos<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>repo_name<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>path<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%pom.xml&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>repo_name<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span></code></pre></div><p>There are 131,037 repositories with Maven pom.xml files, compared to the 102,803 repositories with Gradle build files we counted earlier (about only 27% more). It seems Gradle is catching up with Maven!</p>
<h2 id="gradle-build-file-names">Gradle build file names</h2>
<p>Bigger projects tend to split their build tasks under different build files. I was curious to see which kind of split developers did by looking at the most frequent build file names:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>f,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(f)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LAST</span>(SPLIT(path,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;/&#39;</span>))<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span>f<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.gradle_build_files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>f<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">DESC</span><span style="color:#bbb">
</span></span></span></code></pre></div><p><figure>
  <a href="#img-f39cf0a083d722565e2fa96a07f72a37">
    <img src="/img/bigquery-gradle/gh22-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-f39cf0a083d722565e2fa96a07f72a37">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh22-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-e4ed5c502192989e7b18b367d70d7425">
    <img src="/img/bigquery-gradle/gh22-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e4ed5c502192989e7b18b367d70d7425">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh22-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Of course, build.gradle comes first. Followed by settings.gradle. Notice the number of build files which are related to making releases, publishing / deploying the artifacts to a repository. There are also a few checking the quality of the code base, using checkstyle for style violations, JaCoCo for code coverage.</p>
<h2 id="gradle-versions">Gradle versions</h2>
<p>Gradle projects often use the Gradle wrapper to help developers use a particular and consistent version of Gradle, without necessiting Gradle to be installed locally. For those developers who decided to commit their Gradle wrapper in Github, we can have a look at the breakdown of Gradle versions currently in the wild:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">version</span>,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="color:#007020;font-weight:bold">version</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39;gradle-(.*)-(?:all|bin).zip&#39;</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">version</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span>line<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.gradle_wrapper_properties_files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>line<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;distributionUrl%&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">version</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">DESC</span><span style="color:#bbb">
</span></span></span></code></pre></div><p><figure>
  <a href="#img-4575c47535e3dc5b4c2adb2bce719e9c">
    <img src="/img/bigquery-gradle/gh20a-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-4575c47535e3dc5b4c2adb2bce719e9c">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh20a-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-9ceb5e1e5a159f11b5202a51d5679c6a">
    <img src="/img/bigquery-gradle/gh20a-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9ceb5e1e5a159f11b5202a51d5679c6a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh20a-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>It looks like Gradle 2.4 was a big hit!</p>
<h2 id="gradle-plugins">Gradle plugins</h2>
<p>Gradle projects often take advantage of third-party plugins. You&rsquo;ll see plugins declared with the &ldquo;id&rdquo; syntax or applied with &ldquo;apply plugin&rdquo;. Let&rsquo;s looked at both:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>plugin,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(plugin)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39;apply plugin: (?:\&#39;</span><span style="color:#666">|</span><span style="">\</span><span style="color:#4070a0">&#34;)(.*)(?:\&#39;|\&#34;</span>)<span style="color:#4070a0">&#39;) AS plugin
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  FROM (
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    SELECT SPLIT(content, &#39;</span><span style="">\</span>n<span style="color:#4070a0">&#39;) AS line
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    FROM [github-groovy-files:github.gradle_build_contents]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  )
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">GROUP BY plugin
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">ORDER BY count DESC
</span></span></span></code></pre></div><p><figure>
  <a href="#img-21ca29d20f8b7a97f3c406341e808507">
    <img src="/img/bigquery-gradle/gh23-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-21ca29d20f8b7a97f3c406341e808507">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh23-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-542307a09687961d4e0dd92c2f022b91">
    <img src="/img/bigquery-gradle/gh23-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-542307a09687961d4e0dd92c2f022b91">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh23-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Look at the big number of Android related plugins! Clearly, Android adopting Gradle as build solution gave a big boost to Gradle&rsquo;s adoption!</p>
<p>The plugins declared with &ldquo;id&rdquo; show another story though:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>newplugin,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(newplugin)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39;id (?:\&#39;</span><span style="color:#666">|</span><span style="">\</span><span style="color:#4070a0">&#34;)(.*)(?:\&#39;|\&#34;</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">version</span><span style="color:#4070a0">&#39;) AS newplugin
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  FROM (
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    SELECT SPLIT(content, &#39;</span><span style="">\</span>n<span style="color:#4070a0">&#39;) AS line
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    FROM [github-groovy-files:github.gradle_build_contents]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  )
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">GROUP BY newplugin
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">ORDER BY count DESC
</span></span></span></code></pre></div><p><figure>
  <a href="#img-9a5383096d53d0aff854d327cf4dc795">
    <img src="/img/bigquery-gradle/gh24-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-9a5383096d53d0aff854d327cf4dc795">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh24-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-702cb36111160f1e996d0252cb7400cc">
    <img src="/img/bigquery-gradle/gh24-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-702cb36111160f1e996d0252cb7400cc">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh24-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Here, we see a big usage of the Bintray plugin and the shadow plugin.</p>
<h2 id="build-dependencies">Build dependencies</h2>
<p>Now it&rsquo;s time to look at dependencies. First, the &ldquo;compile&rdquo; dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>dep,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(dep)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39;compile(?: |\()(?:\&#39;</span><span style="color:#666">|</span><span style="">\</span><span style="color:#4070a0">&#34;)(.*):&#39;) AS dep
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  FROM (
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    SELECT SPLIT(content, &#39;\n&#39;) AS line
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    FROM [github-groovy-files:github.gradle_build_contents]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  )
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">GROUP BY dep
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">ORDER BY count DESC
</span></span></span></code></pre></div><p><figure>
  <a href="#img-965714f76eca56d43bae70f726382ab1">
    <img src="/img/bigquery-gradle/gh25-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-965714f76eca56d43bae70f726382ab1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh25-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-3ef86dc9995501b2bb4d6d35928078aa">
    <img src="/img/bigquery-gradle/gh25-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3ef86dc9995501b2bb4d6d35928078aa">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh25-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Again, there&rsquo;s a big usage of Android related dependencies. We also notice Spring Boot, GSON, Guava, SLF4J, Retrofit, Jackson.</p>
<p>For the test dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>dep,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(dep)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39;testCompile(?: |\()(?:\&#39;</span><span style="color:#666">|</span><span style="">\</span><span style="color:#4070a0">&#34;)(.*):&#39;) AS dep
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  FROM (
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    SELECT SPLIT(content, &#39;\n&#39;) AS line
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">    FROM [github-groovy-files:github.gradle_build_contents]
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">  )
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">GROUP BY dep
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">ORDER BY count DESC
</span></span></span></code></pre></div><p><figure>
  <a href="#img-db751fa2283bdbaae68c496317963962">
    <img src="/img/bigquery-gradle/gh26-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-db751fa2283bdbaae68c496317963962">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh26-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-d6f08ea97831f2eb98b0b4a06beeee98">
    <img src="/img/bigquery-gradle/gh26-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d6f08ea97831f2eb98b0b4a06beeee98">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bigquery-gradle/gh26-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>No big surprise with JUnit coming first. But we have Spock, Mockito&rsquo;s mocking library, AssertJ assertions, Hamcrest matchers.</p>
<h2 id="summary">Summary</h2>
<p>And this wraps up our analysis of Gradle build files, thanks to <a href="https://cloud.google.com/bigquery/">Google BigQuery</a> and the Github dataset. It&rsquo;s interesting to see that Gradle has gained a very significant market share, coming pretty close to the Maven incumbent, and to see lots of Android projects are on Github with Gradle builds.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>My G3 Summit Apache Groovy Keynote</title><link>https://glaforge.dev/talks/2016/11/29/my-g3-summit-apache-groovy-keynote/</link><pubDate>Tue, 29 Nov 2016 16:30:57 +0100</pubDate><guid>https://glaforge.dev/talks/2016/11/29/my-g3-summit-apache-groovy-keynote/</guid><description>&lt;p>This week, I&amp;rsquo;m in Florida for the brand new &lt;a href="https://g3summit.com/conference/fort_lauderdale/2016/11/home">G3 Summit&lt;/a> conference, dedicated to the Apache Groovy ecosystem (Grails, Gradle, and more). I had the chance of giving the keynote, where I gave an overview of the Apache Groovy project&amp;rsquo;s philosophy, history, and where it&amp;rsquo;s heading. In the second part, I&amp;rsquo;m showcasing the new features, new syntax constructs, already there or coming in Groovy 2.4.x, in the future Groovy 2.5, and in Groovy 3.0 with the new parser.&lt;/p></description><content:encoded>
<![CDATA[<p>This week, I&rsquo;m in Florida for the brand new <a href="https://g3summit.com/conference/fort_lauderdale/2016/11/home">G3 Summit</a> conference, dedicated to the Apache Groovy ecosystem (Grails, Gradle, and more). I had the chance of giving the keynote, where I gave an overview of the Apache Groovy project&rsquo;s philosophy, history, and where it&rsquo;s heading. In the second part, I&rsquo;m showcasing the new features, new syntax constructs, already there or coming in Groovy 2.4.x, in the future Groovy 2.5, and in Groovy 3.0 with the new parser.</p>
<script async class="speakerdeck-embed" data-id="ceb97d1f87cc4e2b810914178bb692a9" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Billions of lines of code in a single repository, seriously?</title><link>https://glaforge.dev/talks/2016/11/23/billions-of-lines-of-code-in-a-single-repository-seriously/</link><pubDate>Wed, 23 Nov 2016 16:35:08 +0100</pubDate><guid>https://glaforge.dev/talks/2016/11/23/billions-of-lines-of-code-in-a-single-repository-seriously/</guid><description>&lt;p>When I joined Google last June, I discovered a new world: tons of new acronyms or project code names to learn about, but also a particular environment for your source code. At Google, engineers work on a huge monolithic source code repository comprising of: &lt;/p>
&lt;ul>
&lt;li>1 billion files&lt;/li>
&lt;li>9 million source files&lt;/li>
&lt;li>2 billion lines of code&lt;/li>
&lt;li>35 million commits&lt;/li>
&lt;li>86 terabytes of content&lt;/li>
&lt;li>45 thousands of commits every day.&lt;/li>
&lt;/ul>
&lt;p>Rachel Potvin, who&amp;rsquo;s an engineering manager at Google, wrote &lt;a href="http://cacm.acm.org/magazines/2016/7/204032-why-google-stores-billions-of-lines-of-code-in-a-single-repository/fulltext">an article for ACM&lt;/a> about how Google handles such a huge repository, as well as the tools and practices around that. Wired also covered the topic in their article &amp;ldquo;&lt;a href="https://www.wired.com/2015/09/google-2-billion-lines-codeand-one-place">Google is 2 billion lines of code and it&amp;rsquo;s all in one place&lt;/a>&amp;rdquo;. And Rachel also &lt;a href="https://www.youtube.com/watch?v=W71BTkUbdqE">presented this topic at the @Scale conference&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>When I joined Google last June, I discovered a new world: tons of new acronyms or project code names to learn about, but also a particular environment for your source code. At Google, engineers work on a huge monolithic source code repository comprising of: </p>
<ul>
<li>1 billion files</li>
<li>9 million source files</li>
<li>2 billion lines of code</li>
<li>35 million commits</li>
<li>86 terabytes of content</li>
<li>45 thousands of commits every day.</li>
</ul>
<p>Rachel Potvin, who&rsquo;s an engineering manager at Google, wrote <a href="http://cacm.acm.org/magazines/2016/7/204032-why-google-stores-billions-of-lines-of-code-in-a-single-repository/fulltext">an article for ACM</a> about how Google handles such a huge repository, as well as the tools and practices around that. Wired also covered the topic in their article &ldquo;<a href="https://www.wired.com/2015/09/google-2-billion-lines-codeand-one-place">Google is 2 billion lines of code and it&rsquo;s all in one place</a>&rdquo;. And Rachel also <a href="https://www.youtube.com/watch?v=W71BTkUbdqE">presented this topic at the @Scale conference</a>.</p>
<blockquote>
<p>Google stores all its source code in one single monolithic repository! Imagine 25,000 software developers working simultaneously on 86 TB of data, including two billion lines of code in 9 million unique source files. Each week, there are as many lines of code changed as there are lines in the full Linux kernel repository. How does Google’s source code works at this scale? What are the advantages and drawbacks of such an approach? Come and learn about what it means to work on such a big mammoth repository.</p></blockquote>
<p>You can find the slide deck embedded below:</p>
<script async class="speakerdeck-embed" data-id="d3714d006f694a9f915b8637bb192eea" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>And the talk was also recorded, so you can view the video on Devoxx&rsquo;s YouTube channel here:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/yM0GQw1zgrA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Becoming Twitter verified</title><link>https://glaforge.dev/posts/2016/11/15/becoming-twitter-verified/</link><pubDate>Tue, 15 Nov 2016 16:50:50 +0100</pubDate><guid>https://glaforge.dev/posts/2016/11/15/becoming-twitter-verified/</guid><description>&lt;p>Probably for vanity sake, or perhaps even out of jealousy seeing friends becoming &amp;ldquo;&lt;a href="https://support.twitter.com/articles/119135">twitter verified&lt;/a>&amp;rdquo;, I was curious to see if, me too, I could get those little ticks beside my name on my &lt;a href="https://twitter.com/glaforge">Twitter profile&lt;/a>.&lt;/p>
&lt;p>Generally speaking, verified accounts are accounts of &amp;ldquo;public interest&amp;rdquo;. It can range from your usual movie stars, to politicians, from well-known artists, to company CEOs, but also persons somehow well known in the twittosphere, including tech luminaries, representative of particular tech communities, etc. So, seeing my tech friends getting the little tick, I thought I should try that out.&lt;/p></description><content:encoded>
<![CDATA[<p>Probably for vanity sake, or perhaps even out of jealousy seeing friends becoming &ldquo;<a href="https://support.twitter.com/articles/119135">twitter verified</a>&rdquo;, I was curious to see if, me too, I could get those little ticks beside my name on my <a href="https://twitter.com/glaforge">Twitter profile</a>.</p>
<p>Generally speaking, verified accounts are accounts of &ldquo;public interest&rdquo;. It can range from your usual movie stars, to politicians, from well-known artists, to company CEOs, but also persons somehow well known in the twittosphere, including tech luminaries, representative of particular tech communities, etc. So, seeing my tech friends getting the little tick, I thought I should try that out.</p>
<p>And on my first try&hellip; I failed&hellip;</p>
<p>I didn&rsquo;t completely failed, as I didn&rsquo;t get the usual message saying I failed verification, but that some tweaks could be made to my profile to add some more information. They asked whether the account name reflected the real person&rsquo;s name (in my case &lsquo;glaforge&rsquo; maps well to my name!), and if my government issued photo ID was legible or not (and it was the case too). So I was puzzled, and didn&rsquo;t really get actionable items to pursue.</p>
<p>For my first attempt, I initially scanned my national ID card, but I guess it&rsquo;s not a very well known format, so on my second try, I instead scanned my passport. Even if I&rsquo;m not feeling safe sending a scan of my ID&hellip; but that&rsquo;s for another story.</p>
<p>I didn&rsquo;t change anything, but two months later, I tried again, and this time it worked!</p>
<p><figure>
  <a href="#img-cccaf7a5be5be147a97f3234976ccd91">
    <img src="/img/misc/glaforge-twitter-verified.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-cccaf7a5be5be147a97f3234976ccd91">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/glaforge-twitter-verified.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>After bragging about being verified on Twitter, I got several DM conversations asking for advice. So I&rsquo;ll just repeat some of the advice given by Twitter themselves, and add my own into the loop.</p>
<p>First, be sure to read the following articles from Twitter:</p>
<ul>
<li><a href="https://support.twitter.com/articles/119135">about verified accounts</a></li>
<li><a href="https://support.twitter.com/articles/20174630">verified accounts</a></li>
<li><a href="https://support.twitter.com/articles/20174631">request to verify an account</a></li>
</ul>
<p>The last one is the most important of the three.</p>
<p>You should really have:</p>
<ul>
<li>a <strong>verified phone number</strong>: It&rsquo;ll be checked with a text message and a code, pretty easy step.</li>
<li>a <strong>confirmed email address</strong>: Same principle, be sure to use a real email address, and a similar verification step takes place.</li>
<li>a <strong>bio</strong>: I think the bio is pretty important. It describes the &ldquo;public&rdquo; person that you are. So if you&rsquo;re a singer, mention that you&rsquo;re a singer and give a link that proves that you are one, for example the website of your band. During the verification, you&rsquo;ll be asked for up to 5 links confirming who you are, so don&rsquo;t hesitate to reuse one of those links in your bio, or related twitter account, etc.</li>
<li>a <strong>header photo</strong>: I don&rsquo;t think the content of the header photo really matters, but verified accounts need to have a header picture. Even if it&rsquo;s just a nice landscape, it should be there, instead of just the colored background.</li>
<li>a <strong>birthday</strong>: I&rsquo;m not sure how important this really is. But if you indicate your birthday, it better be the same date as the one provided on your ID scan obviously!</li>
<li>a <strong>website</strong>: For the website, I used the URL of my blog. And this is also one of the URLs I&rsquo;ve given in the form to request verification. This URL should point at a place that can prove who you are: on my blog, there&rsquo;s a section about me, describing what I&rsquo;m doing in life.</li>
<li><strong>public tweets</strong>: your tweets should be publicly visible, otherwise, no point in requesting being verified!</li>
</ul>
<p>The documentation says it&rsquo;s better if the account name reflects the real name of the person. So it might help. But if you&rsquo;re known with a particular Twitter handle, I don&rsquo;t think you really need to change it for the sake of becoming verified. But it might be harder with a strange Twitter handle than a handle that resembles your name.</p>
<p>When you fill the form for requesting verification, you will be asked for up to five links. In my case, I gave the URL of my blog (which, as I said, has a section explaining who I am, shows my real name, and also a picture of myself, so they can check the Twitter avatar, the photo ID as well). I also gave my LinkedIn profile, my Google+ profile. And I think that&rsquo;s all. Anything that proves you&rsquo;re who you claim to be can be useful.</p>
<p>Last but not least, there&rsquo;s a form field where you have to justify why you request becoming verified. In my case, I wanted cover the two &ldquo;aspects&rdquo; of my life: at day, I&rsquo;m a Developer Advocate for Google, and at night, I&rsquo;m working on the Apache Groovy open source project. For reference, here&rsquo;s the blurb that I used:</p>
<blockquote>
<p>Leading the Apache Groovy project, I&rsquo;m a spokesperson for the successful open source project, and for the ecosystem &amp; community around it. In my day life, I&rsquo;m also a Developer Advocate, at Google, on their Cloud Platform. Having my Twitter account verified would be an additional stamp of approval for my involvement in the Groovy community and with the company&rsquo;s product I advocate for.</p></blockquote>
<p>After my first attempt two months ago, I tried again over the weekend, and the next day, I got the email confirming my account was verified! Yay!</p>
<p>I hope this article is helpful. Don&rsquo;t hesitate to share your own tricks in the comments. At least, following the advice above, I was able to get verified. I have about 11K followers, but I&rsquo;ve seen people with less than half as many followers also become verified. So the number of followers is not everything. But I think paying attention to the quality of your profile is what ultimately pays off. So good luck with your verification process! And don&rsquo;t hesitate to share your own tips in the comments below.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Binge streaming web APIs with Ratpack, Cloud Ednpoints, App Engine Flex and Streamdata.io</title><link>https://glaforge.dev/talks/2016/11/15/binge-streaming-web-apis-with-ratpack-cloud-ednpoints-app-engine-flex-and-streamdata-io/</link><pubDate>Tue, 15 Nov 2016 16:40:32 +0100</pubDate><guid>https://glaforge.dev/talks/2016/11/15/binge-streaming-web-apis-with-ratpack-cloud-ednpoints-app-engine-flex-and-streamdata-io/</guid><description>&lt;p>At &lt;a href="https://devoxx.be/">Devoxx&lt;/a> last week, I had the chance to do a joint tools-in-action with my talented friend Audrey Neveu, titled Binge streaming you Web API:&lt;/p>
&lt;blockquote>
&lt;p>In a fast-paced fashion, to keep you awake after long University sessions, Audrey and Guillaume will set you up to create a Web API using Google Cloud Endpoints, and stream the content of the API in real-time with Streamdata.io. After a quick introduction to both technologies, they’ll build together both the backend and the front-end to interact live with the audience, through the Web or via a mobile app.&lt;/p></description><content:encoded>
<![CDATA[<p>At <a href="https://devoxx.be/">Devoxx</a> last week, I had the chance to do a joint tools-in-action with my talented friend Audrey Neveu, titled Binge streaming you Web API:</p>
<blockquote>
<p>In a fast-paced fashion, to keep you awake after long University sessions, Audrey and Guillaume will set you up to create a Web API using Google Cloud Endpoints, and stream the content of the API in real-time with Streamdata.io. After a quick introduction to both technologies, they’ll build together both the backend and the front-end to interact live with the audience, through the Web or via a mobile app.</p></blockquote>
<p>For the impatient, scroll down to the end of the article to access the slides presented, and view a recording of the video!</p>
<p>We split the presentation in two sections: 1) first of all, we obviously need to create and deploy a Web API, and then 2) to configure and use <a href="http://streamdata.io/">Streamdata.io</a> to stream the updates live, rather than poll the API endlessly.</p>
<p>For the purpose of our demo, Audrey and myself decided to surf on the theme of the conference, by publishing ourselves an API of the conference content, listing all the talks and speakers available.</p>
<p>As the content is pretty much static, we needed some data that would evolve in real-time as well. We added a voting capability, so that users could click on a little smiley to say if they&rsquo;re enjoying the talk or not. With the streaming capability, as soon as votes are taking place, we update the UI with the new vote results.</p>
<h2 id="implementing-my-api-with-ratpack">Implementing my API with Ratpack</h2>
<p>To build the API, I decided to go with the <a href="https://ratpack.io/">Ratpack framework</a>. As the Ratpack website states:</p>
<p>Ratpack is a set of Java libraries for building modern HTTP applications.</p>
<p>It provides just enough for writing practical, high performance, apps.</p>
<p>It is built on Java 8, Netty and reactive principles.</p>
<p>You can use Ratpack with Java 8, but there&rsquo;s also a nice Groovy wrapper. So with my <a href="http://www.groovy-lang.org/">Apache Groovy</a> hat on, I naturally chose to go with Groovy!</p>
<p>In the Groovy community, there&rsquo;s a tool called <a href="https://github.com/pledbrook/lazybones">Lazybones</a> which allows you to create template projects easily. And we&rsquo;re also using <a href="http://sdkman.io/">SDKman</a> for installing various SDKs, including SDKman.</p>
<p>If you don&rsquo;t have lazybones installed (but have SDKman), it&rsquo;s fairly easy to install:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sdk install lazybones
</span></span></code></pre></div><p>With both installed already on my machine, I just needed to create a new project with the Ratpack template:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>lazybones create ratpack
</span></span></code></pre></div><p>And I had my template project ready!</p>
<p>Gradle to the rescue to build the app</p>
<p>My Gradle script is pretty straightforward, using the Ratpack Groovy plugin, the Shadow plugin, etc. Nothing really fancy:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>buildscript <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    repositories <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        jcenter<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#34;io.ratpack:ratpack-gradle:1.4.4&#34;</span>
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#34;com.github.jengelman.gradle.plugins:shadow:1.2.3&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#34;io.ratpack.ratpack-groovy&#34;</span>
</span></span><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#34;com.github.johnrengelman.shadow&#34;</span>
</span></span><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#34;idea&#34;</span>
</span></span><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#34;eclipse&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>repositories <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    jcenter<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    runtime <span style="color:#4070a0">&#39;org.slf4j:slf4j-simple:1.7.21&#39;</span>
</span></span><span style="display:flex;"><span>    testCompile <span style="color:#4070a0">&#34;org.spockframework:spock-core:1.0-groovy-2.4&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>To build this app, I&rsquo;ll use the tar distribution target, which generates startup scripts to be launched from the command-line:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew distTar
</span></span></code></pre></div><p>To run the app locally, you can use:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew run
</span></span></code></pre></div><p>By default, it runs on port 5050.</p>
<p>The URL paths and methods</p>
<p>For developing my API, I created the following paths and methods:</p>
<ul>
<li>GET /api : to list all the talks available</li>
<li>GET /api/{day} : to restrict the list of talks to just one particular day</li>
<li>GET /api/talk/{id} : to view the details of a particular talk</li>
<li>POST /api/talk/{id}/vote/{vote} : to vote on a particular talk (negative, neutral, positive)</li>
<li>POST /import : to import a JSON dump of all the talks, but it&rsquo;s just used by me for uploading the initial content, so it&rsquo;s not really part of my API.</li>
</ul>
<h2 id="implementing-the-api">Implementing the API</h2>
<p>My <a href="https://ratpack.io/">Ratpack</a> app implementing this API (plus a few other URLs) spans a hundred lines of Groovy code or so (including blank lines, imports, and curly braces). The implementation is a bit naive as I&rsquo;m storing the talks / speakers data in memory, but I should have used a backend storage like <a href="https://cloud.google.com/datastore/">Cloud Datastore</a> or <a href="https://cloud.google.com/sql/">Cloud SQL</a>, potentially with Memcache in front. So the app won&rsquo;t scale well and data will not be synchronized across multiple instances running in parallel. For the sake of my demo though, that was sufficient!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">ratpack.handling.RequestLogger</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">org.slf4j.LoggerFactory</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">static</span> ratpack<span style="color:#666">.</span><span style="color:#4070a0">groovy</span><span style="color:#666">.</span><span style="color:#4070a0">Groovy</span><span style="color:#666">.</span><span style="color:#4070a0">ratpack</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">static</span> ratpack<span style="color:#666">.</span><span style="color:#4070a0">jackson</span><span style="color:#666">.</span><span style="color:#4070a0">Jackson</span><span style="color:#666">.</span><span style="color:#4070a0">json</span> <span style="color:#007020;font-weight:bold">as</span> toJson
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">static</span> ratpack<span style="color:#666">.</span><span style="color:#4070a0">jackson</span><span style="color:#666">.</span><span style="color:#4070a0">Jackson</span><span style="color:#666">.</span><span style="color:#4070a0">fromJson</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> log <span style="color:#666">=</span> LoggerFactory<span style="color:#666">.</span><span style="color:#4070a0">getLogger</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;Devoxx&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> allTalks <span style="color:#666">=</span> <span style="color:#666">[]</span>
</span></span><span style="display:flex;"><span>ratpack <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    handlers <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        all<span style="color:#666">(</span>RequestLogger<span style="color:#666">.</span><span style="color:#4070a0">ncsa</span><span style="color:#666">())</span>
</span></span><span style="display:flex;"><span>        post<span style="color:#666">(</span><span style="color:#4070a0">&#39;import&#39;</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            log<span style="color:#666">.</span><span style="color:#4070a0">info</span> <span style="color:#4070a0">&#34;Importing talks dump&#34;</span>
</span></span><span style="display:flex;"><span>            byContent <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                json <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    parse<span style="color:#666">(</span>fromJson<span style="color:#666">(</span>List<span style="color:#666">)).</span><span style="color:#4070a0">onError</span> <span style="color:#666">{</span> e <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>                        String msg <span style="color:#666">=</span> <span style="color:#4070a0">&#34;Import failed: $e&#34;</span>
</span></span><span style="display:flex;"><span>                        log<span style="color:#666">.</span><span style="color:#4070a0">error</span> msg
</span></span><span style="display:flex;"><span>                        response<span style="color:#666">.</span><span style="color:#4070a0">status</span><span style="color:#666">(</span><span style="color:#40a070">400</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                        render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> <span style="color:#4070a0">&#34;Import failed: $e&#34;</span><span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}.</span><span style="color:#4070a0">then</span> <span style="color:#666">{</span> talks <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>                        allTalks <span style="color:#666">=</span> talks
</span></span><span style="display:flex;"><span>                        log<span style="color:#666">.</span><span style="color:#4070a0">info</span> <span style="color:#4070a0">&#34;Loaded ${allTalks.size()} talks&#34;</span>
</span></span><span style="display:flex;"><span>                        render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> <span style="color:#4070a0">&#39;Import successful&#39;</span><span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>        prefix<span style="color:#666">(</span><span style="color:#4070a0">&#39;api&#39;</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            prefix<span style="color:#666">(</span><span style="color:#4070a0">&#39;talk&#39;</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                post<span style="color:#666">(</span><span style="color:#4070a0">&#39;:id/vote/:vote&#39;</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#902000">def</span> aTalk <span style="color:#666">=</span> allTalks<span style="color:#666">.</span><span style="color:#4070a0">find</span> <span style="color:#666">{</span> it<span style="color:#666">.</span><span style="color:#4070a0">id</span> <span style="color:#666">==</span> pathTokens<span style="color:#666">.</span><span style="color:#4070a0">id</span> <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>aTalk<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#902000">def</span> msg <span style="color:#666">=</span> <span style="color:#4070a0">&#34;Voted $pathTokens.vote on talk $pathTokens.id&#34;</span><span style="color:#666">.</span><span style="color:#4070a0">toString</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#007020;font-weight:bold">switch</span> <span style="color:#666">(</span>pathTokens<span style="color:#666">.</span><span style="color:#4070a0">vote</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#007020;font-weight:bold">case</span> <span style="color:#4070a0">&#34;negative&#34;</span><span style="color:#666">:</span>
</span></span><span style="display:flex;"><span>                                aTalk<span style="color:#666">.</span><span style="color:#4070a0">reactions</span><span style="color:#666">.</span><span style="color:#4070a0">negative</span> <span style="color:#666">+=</span> <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>                                log<span style="color:#666">.</span><span style="color:#4070a0">info</span> msg
</span></span><span style="display:flex;"><span>                                render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> msg<span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                                <span style="color:#007020;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#007020;font-weight:bold">case</span> <span style="color:#4070a0">&#34;neutral&#34;</span><span style="color:#666">:</span>
</span></span><span style="display:flex;"><span>                                aTalk<span style="color:#666">.</span><span style="color:#4070a0">reactions</span><span style="color:#666">.</span><span style="color:#4070a0">neutral</span> <span style="color:#666">+=</span> <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>                                log<span style="color:#666">.</span><span style="color:#4070a0">info</span> msg
</span></span><span style="display:flex;"><span>                                render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> msg<span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                                <span style="color:#007020;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#007020;font-weight:bold">case</span> <span style="color:#4070a0">&#34;positive&#34;</span><span style="color:#666">:</span>
</span></span><span style="display:flex;"><span>                                aTalk<span style="color:#666">.</span><span style="color:#4070a0">reactions</span><span style="color:#666">.</span><span style="color:#4070a0">positive</span> <span style="color:#666">+=</span> <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>                                log<span style="color:#666">.</span><span style="color:#4070a0">info</span> msg
</span></span><span style="display:flex;"><span>                                render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> msg<span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                                <span style="color:#007020;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#007020;font-weight:bold">default</span><span style="color:#666">:</span>
</span></span><span style="display:flex;"><span>                                response<span style="color:#666">.</span><span style="color:#4070a0">status</span><span style="color:#666">(</span><span style="color:#40a070">400</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                                msg <span style="color:#666">=</span> <span style="color:#4070a0">&#34;&#39;${pathTokens.vote}&#39; is not a valid vote&#34;</span><span style="color:#666">.</span><span style="color:#4070a0">toString</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>                                log<span style="color:#666">.</span><span style="color:#4070a0">info</span> msg
</span></span><span style="display:flex;"><span>                                render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> msg<span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">else</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                        response<span style="color:#666">.</span><span style="color:#4070a0">status</span><span style="color:#666">(</span><span style="color:#40a070">404</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                        render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> <span style="color:#4070a0">&#34;Talk $pathTokens.id not found&#34;</span><span style="color:#666">.</span><span style="color:#4070a0">toString</span><span style="color:#666">()])</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                get<span style="color:#666">(</span><span style="color:#4070a0">&#39;:id&#39;</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#902000">def</span> aTalk <span style="color:#666">=</span> allTalks<span style="color:#666">.</span><span style="color:#4070a0">find</span> <span style="color:#666">{</span> it<span style="color:#666">.</span><span style="color:#4070a0">id</span> <span style="color:#666">==</span> pathTokens<span style="color:#666">.</span><span style="color:#4070a0">id</span> <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>aTalk<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                        log<span style="color:#666">.</span><span style="color:#4070a0">info</span> <span style="color:#4070a0">&#34;Found talk: $pathTokens.id&#34;</span>
</span></span><span style="display:flex;"><span>                        render <span style="color:#06287e">toJson</span><span style="color:#666">(</span>aTalk<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">else</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                        String msg <span style="color:#666">=</span> <span style="color:#4070a0">&#34;Talk $pathTokens.id not found&#34;</span>
</span></span><span style="display:flex;"><span>                        log<span style="color:#666">.</span><span style="color:#4070a0">info</span> msg
</span></span><span style="display:flex;"><span>                        response<span style="color:#666">.</span><span style="color:#4070a0">status</span><span style="color:#666">(</span><span style="color:#40a070">404</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                        render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> msg<span style="color:#666">])</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>            get<span style="color:#666">(</span><span style="color:#4070a0">&#39;:day&#39;</span><span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                <span style="color:#902000">def</span> talksPerDay <span style="color:#666">=</span> allTalks<span style="color:#666">.</span><span style="color:#4070a0">findAll</span> <span style="color:#666">{</span> it<span style="color:#666">.</span><span style="color:#4070a0">day</span> <span style="color:#666">==</span> pathTokens<span style="color:#666">.</span><span style="color:#4070a0">day</span> <span style="color:#666">}.</span><span style="color:#4070a0">collect</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    it<span style="color:#666">.</span><span style="color:#4070a0">subMap</span><span style="color:#666">(</span>it<span style="color:#666">.</span><span style="color:#4070a0">keySet</span><span style="color:#666">()</span> <span style="color:#666">-</span> <span style="color:#4070a0">&#39;summary&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>talksPerDay<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    render <span style="color:#06287e">toJson</span><span style="color:#666">(</span>talksPerDay<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">else</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    response<span style="color:#666">.</span><span style="color:#4070a0">status</span><span style="color:#666">(</span><span style="color:#40a070">404</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                    render <span style="color:#06287e">toJson</span><span style="color:#666">([</span><span style="color:#002070;font-weight:bold">status:</span> <span style="color:#4070a0">&#34;Invalid day, or no talks found for: $pathTokens.day&#34;</span><span style="color:#666">.</span><span style="color:#4070a0">toString</span><span style="color:#666">()])</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>            get <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                render <span style="color:#06287e">toJson</span><span style="color:#666">(</span>request<span style="color:#666">.</span><span style="color:#4070a0">queryParams</span><span style="color:#666">.</span><span style="color:#4070a0">full</span> <span style="color:#666">?</span> allTalks <span style="color:#666">:</span> allTalks<span style="color:#666">.</span><span style="color:#4070a0">collect</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                    it<span style="color:#666">.</span><span style="color:#4070a0">subMap</span><span style="color:#666">(</span>it<span style="color:#666">.</span><span style="color:#4070a0">keySet</span><span style="color:#666">()</span> <span style="color:#666">-</span> <span style="color:#4070a0">&#39;summary&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">})</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Some interesting points about this code:</p>
<ul>
<li>use of an NCSA-compliant request logger to log all API calls (following the NCSA usual output pattern) and a dedicated logger for important events, and you&rsquo;re able to watch both those kind of logs in the Stackdriver logging in the cloud console</li>
<li>the use of prefix(&rsquo;&hellip;&rsquo;) to factor common path parts</li>
<li>Jackson is being used both for parsing (the input file containing the initial list of talks) as well as for output for rendering the JSON payloads</li>
<li>see how we use byContent / json to handle the requests coming up with content-type of application/json</li>
<li>the rest is essentially error handling, collection filtering, etc.</li>
</ul>
<h2 id="containerizing-the-app">Containerizing the app</h2>
<p>Ratpack requires JDK 8 to run, and it&rsquo;s not based on servlets, I went with <a href="https://cloud.google.com/appengine/docs/flexible/">Google App Engine Flex</a>, which allows me to run JDK 8 / Java 8 apps which can be based on networking toolkits like <a href="http://netty.io/">Netty</a>.</p>
<p>I mentioned we&rsquo;re using the distTar target to build a distribution of the application, and that&rsquo;s what we&rsquo;ll point our Dockerfile at, as App Engine Flex allows you to customize Docker images for bundling and running your app:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#4070a0"> gcr.io/google_appengine/openjdk8</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">VOLUME</span><span style="color:#4070a0"> /tmp</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">RUN</span> mkdir -p /app/endpoints<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ADD</span> service.json /app/endpoints<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ADD</span> build/distributions/devoxx-reactions.tar /<span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ENV</span> <span style="color:#bb60d5">JAVA_OPTS</span><span style="color:#666">=</span><span style="color:#4070a0">&#39;-Dratpack.port=8080 -Djava.security.egd=file:/dev/./urandom&#39;</span><span style="">
</span></span></span><span style="display:flex;"><span><span style=""></span><span style="color:#007020;font-weight:bold">ENTRYPOINT</span> [<span style="color:#4070a0">&#34;/devoxx-reactions/bin/devoxx-reactions&#34;</span>]<span style="">
</span></span></span></code></pre></div><p>I&rsquo;m using the dedicated Open JDK 8 image for App Engine, from the <a href="https://cloud.google.com/container-registry/">Google Container Registry</a>. I&rsquo;m specifying the port for my app, the entry point to the startup scripts. You&rsquo;ll notice the lines about &ldquo;endpoints&rdquo; and &ldquo;service.json&rdquo;, and I&rsquo;ll come to it in a minute: it&rsquo;s because I&rsquo;m using <a href="https://cloud.google.com/endpoints/">Google Cloud Endpoints</a> to expose and manage my API!</p>
<p>At this point, to feel safer, you can double check that your app is running under docker with something like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew distTar
</span></span><span style="display:flex;"><span>docker build -t devoxx-reactions-image .
</span></span><span style="display:flex;"><span>docker run -p 127.0.0.1:8080:8080 -it devoxx-reactions-image
</span></span></code></pre></div><p>The app is running on port 8080 of your localhost. We&rsquo;ll see later on how to test it with curl, and how to load the sample data that we&rsquo;ve prepared in public/data/talks.json.</p>
<h2 id="gcloud-to-set-up-our-projects">GCloud, to set up our projects</h2>
<p>As we&rsquo;re going to use Cloud Endpoints on App Engine Flex, it&rsquo;s time that I start setting things up with the gcloud SDK:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud init
</span></span><span style="display:flex;"><span>gcloud components update
</span></span><span style="display:flex;"><span>gcloud components install beta
</span></span><span style="display:flex;"><span>gcloud config <span style="color:#007020">set</span> project <span style="color:#666">[</span>YOUR_PROJECT_ID<span style="color:#666">]</span>
</span></span></code></pre></div><h2 id="about-google-cloud-endpoints">About Google Cloud Endpoints</h2>
<p>Google Cloud Platform offers a particular service for managing Web APIs called <a href="https://cloud.google.com/endpoints/">Endpoints</a>. With Endpoints, you&rsquo;re able to monitor your API (see what endpoints, methods, are called, with some nice graphs and stats), to secure your API (with different kind of authentication like Firebase authentication, JSON Web Tokens, API keys, etc.), to scale it.</p>
<p>Speaking of scaling, I&rsquo;m using App Engine Flex here as my deployment target, but it&rsquo;s possible to use Compute Engine or Container Engine as well. Interestingly, you can use Endpoints API management with an API hosted in a third-party cloud, as well as on premises too!</p>
<p>The management aspect of the API is done thanks to a proxy, called the Endpoints Service Proxy, which is implemented on top of NGINX (and which will be open sourced). And to continue on scaling aspect of the story, it&rsquo;s interesting to note that this proxy is living along your app (in its own container). If your API needs to scale across several machines, instead of one giant middle proxy somewhere, your ESP will be duplicated the same way. So there&rsquo;s no single point of failure with a gigantic proxy, but it also means that the latency is super low (below 1 ms usually), because the proxy is as close as possible to your API, without needing any additional costly network hop.</p>
<p>The last interesting aspect about Cloud Endpoints that I&rsquo;d like to mention is that your API contract is defined using <a href="https://openapis.org/">OpenAPI Specs</a> (formerly known as Swagger). So it doesn&rsquo;t matter which language, framework, tech stack you&rsquo;re using to implement your API: as long as you&rsquo;re able to describe your API with an OpenAPI Specification, you&rsquo;re good to go!</p>
<h2 id="specifying-the-contract-of-our-api-with-openapi-spec">Specifying the contract of our API with OpenAPI Spec</h2>
<p>I mentioned before the various resources and methods we&rsquo;re using for our API, and we&rsquo;ll encode these in the form of a contract, using the OpenAPI Spec definition format. In addition to the various resources and methods, we should also define the payloads that will be exchanged: basically, a Talk, a Result, a list of talks. We must define the different status codes for each kind of response. Here&rsquo;s what my specification looks like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0e84b5;font-weight:bold">---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">swagger</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;2.0&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">info</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Consult the Devoxx schedule and vote on your favorite talks.&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">version</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;1.0.0&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Devoxx Reactions&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">contact</span>:<span style="color:#bbb"> </span>{}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">host</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;devoxx-reactions.appspot.com&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">schemes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>- <span style="color:#4070a0">&#34;http&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">paths</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">/import</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">post</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">summary</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Import a dump of the lists of talks&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">operationId</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;ImportTalks&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">consumes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">produces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">parameters</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;talkList&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;body&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/TalkList&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">responses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">200</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 200&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">400</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 400&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">/api</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">get</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">summary</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Get the list of talks&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">operationId</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;GetAllTalks&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">produces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">parameters</span>:<span style="color:#bbb"> </span>[]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">responses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">200</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 200&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;array&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">items</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>/api/talk/{talk}:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">get</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">summary</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Get a particular talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">operationId</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;GetOneTalk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">produces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">parameters</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;path&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">responses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">200</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 200&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">404</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 404&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>/api/talk/{talk}/vote/{vote}:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">post</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">summary</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Vote for a talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">operationId</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;VoteOnTalk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">produces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">parameters</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;path&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;vote&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;path&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;The vote can be \&#34;negative\&#34;, \&#34;neutral\&#34; or \&#34;positive\&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">responses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">200</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 200&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">400</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 400&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">404</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 404&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>/api/{day}:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">get</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">summary</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Get the talks for a particular day&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">operationId</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;GetTalksPerDay&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">produces</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#4070a0">&#34;application/json&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">parameters</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;day&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">in</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;path&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">responses</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">200</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 200&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;array&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">items</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">              </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">404</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Status 404&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">schema</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Result&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#062873;font-weight:bold">definitions</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">Result</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;object&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;status&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">properties</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">status</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;Voting results&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">Talk</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;object&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;day&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;fromTime&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;fromTimeMillis&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;id&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;reactions&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;speakers&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;talkType&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;title&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;toTime&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;toTimeMillis&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>- <span style="color:#4070a0">&#34;track&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">properties</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">day</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">fromTime</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">fromTimeMillis</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;integer&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">format</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;int64&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">id</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">reactions</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;object&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">properties</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">negative</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;integer&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">format</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;int32&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">neutral</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;integer&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">format</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;int32&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">positive</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;integer&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">format</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;int32&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#4070a0">&#34;negative&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#4070a0">&#34;neutral&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#4070a0">&#34;positive&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">room</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">speakers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;array&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">items</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">summary</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">talkType</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">toTime</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">toTimeMillis</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;integer&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">format</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;int64&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">track</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;object&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">properties</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">title</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">          </span><span style="color:#062873;font-weight:bold">trackId</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">            </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;string&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#062873;font-weight:bold">required</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#4070a0">&#34;title&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span>- <span style="color:#4070a0">&#34;trackId&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;A talk representation&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span><span style="color:#062873;font-weight:bold">TalkList</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">type</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;array&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">items</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span><span style="color:#062873;font-weight:bold">$ref</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;#/definitions/Talk&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#062873;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;A list of talks&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>You can write your API specifications using either JSON or YAML. I chose YAML because it&rsquo;s a bit easier to read, for the human eye, and still as much readable for the computer as well.</p>
<p>There&rsquo;s one extra step for instructing Cloud Endpoints about our API definition: I needed to convert my OpenAPI Spec into the service definition format used internally by Cloud Endpoints, thanks to the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud beta service-management convert-config swagger20.yaml service.json
</span></span></code></pre></div><h2 id="deploying-on-app-engine-flex">Deploying on App Engine Flex</h2>
<p>To deploy on App Engine and use Cloud Endpoints, we&rsquo;re going to use the <a href="https://cloud.google.com/sdk/">gcloud</a> command-line tool again:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gcloud beta app deploy
</span></span></code></pre></div><p>After a few minutes, your app / API should be available and be ready for serving.</p>
<h2 id="testing-our-api">Testing our API</h2>
<p>I have a JSON file with the bulk of the talks and their details, so I uploaded it with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -d @src/ratpack/public/data/talks.json -H <span style="color:#4070a0">&#39;Content-Type: application/json&#39;</span> http://devoxx-reactions.appspot.com/import
</span></span></code></pre></div><p>And then I was able to call my API with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl https://devoxx-reactions.appspot.com/api
</span></span><span style="display:flex;"><span>curl https://devoxx-reactions.appspot.com/api/monday
</span></span><span style="display:flex;"><span>curl https://devoxx-reactions.appspot.com/api/talk/HFW-0944
</span></span></code></pre></div><p>And to vote for a given talk with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -X POST https://devoxx-reactions.appspot.com/api/talk/XMX-6190/vote/positive
</span></span></code></pre></div><h2 id="managing-your-api">Managing your API</h2>
<p>When you&rsquo;re visiting your cloud console, you&rsquo;ll be able to see interesting statistics and graphs, about the usage of your API:</p>
<p><figure>
  <a href="#img-7f3bd035db12c4022c67394201344888">
    <img src="/img/binge-streaming/endpoints-manage-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-7f3bd035db12c4022c67394201344888">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/binge-streaming/endpoints-manage-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>

<figure>
  <a href="#img-36ca75d90fa2d6c206100f06c317fcf9">
    <img src="/img/binge-streaming/endpoints-manage-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-36ca75d90fa2d6c206100f06c317fcf9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/binge-streaming/endpoints-manage-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="streaming-the-api">Streaming the API</h2>
<p>For the streaming part of the story, I&rsquo;ll let Audrey cover it! I focused on the backend, how she focused on the Polymer frontend, with a custom Streamdata component, that used the Streamdata proxy to get the patches representing the difference between consecutive calls to the backend. So when the votes were changing (but the rest of the talk details were left unchanged), Streamdata would send back to the front only the diff. In addition to keeping ongoing data exchanges low (in terms of size), the proxy is also able to take care of caching, so it also helps avoiding hitting the backend too often, potentially helping with scalability of the backend, by keeping the cache at the proxy level.</p>
<h2 id="slides-and-video-available">Slides and video available!</h2>
<p>You can watch the video online on Devoxx&rsquo; YouTube channel:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/VT4xsDCeDxE?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And you can also have a closer look at the slides as well:</p>
<script async class="speakerdeck-embed" data-id="87a0f10990e343e29007ea32e49ba0a3" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Latest features of Google Cloud Platform</title><link>https://glaforge.dev/posts/2016/11/13/latest-features-of-google-cloud-platform/</link><pubDate>Sun, 13 Nov 2016 16:53:50 +0100</pubDate><guid>https://glaforge.dev/posts/2016/11/13/latest-features-of-google-cloud-platform/</guid><description>&lt;p>When you&amp;rsquo;re following a project, a company, a platform, you&amp;rsquo;re looking for the latest news, about the latest feature announcement, to take advantage of what&amp;rsquo;s coming up.&lt;/p>
&lt;p>Last time, I blogged about the &lt;a href="https://glaforge.dev/posts/2016/09/28/gcloud-informative-update-message/">gcloud command line tool&lt;/a>, which nicely shows you the latest updates since the last time you updated its components.&lt;/p>
&lt;p>If you go to the Google Cloud Platform website, you&amp;rsquo;ll see dedicated release notes pages for pretty much all products. For example, here are the release notes for:&lt;/p></description><content:encoded>
<![CDATA[<p>When you&rsquo;re following a project, a company, a platform, you&rsquo;re looking for the latest news, about the latest feature announcement, to take advantage of what&rsquo;s coming up.</p>
<p>Last time, I blogged about the <a href="https://glaforge.dev/posts/2016/09/28/gcloud-informative-update-message/">gcloud command line tool</a>, which nicely shows you the latest updates since the last time you updated its components.</p>
<p>If you go to the Google Cloud Platform website, you&rsquo;ll see dedicated release notes pages for pretty much all products. For example, here are the release notes for:</p>
<ul>
<li><a href="https://cloud.google.com/compute/docs/release-notes">Compute Engine</a></li>
<li><a href="https://cloud.google.com/storage/release-notes">Cloud Storage</a></li>
<li><a href="https://cloud.google.com/bigquery/release-notes">BigQuery</a></li>
<li>etc.</li>
</ul>
<p>I&rsquo;ve just discovered a new way to stay updated about what&rsquo;s new. If you go to the <a href="https://console.cloud.google.com/">Cloud Console</a>, click on the little vertical dots, and then preferences:</p>
<p><figure>
  <a href="#img-83a03d655537c582985137218d41e4a9">
    <img src="/img/gcp-new-features/gcp-features-1-prefs.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-83a03d655537c582985137218d41e4a9">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gcp-new-features/gcp-features-1-prefs.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Then, in the main panel, you&rsquo;ll see a new &ldquo;communication&rdquo; section, and if you click on the &ldquo;updates &amp; offers&rdquo;, you&rsquo;ll get a chance to select the option &ldquo;feature announcements&rdquo;:</p>
<p><figure>
  <a href="#img-e1808925c7a25f3debc4ebee240d6729">
    <img src="/img/gcp-new-features/gcp-features-2-announcements.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e1808925c7a25f3debc4ebee240d6729">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/gcp-new-features/gcp-features-2-announcements.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>You&rsquo;ll receive monthly emails about the feature announcements.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Quick intro to Google Cloud Platform for the Paris Ansible meetup</title><link>https://glaforge.dev/talks/2016/11/02/quick-intro-to-gcp-for-the-paris-ansible-meetup/</link><pubDate>Wed, 02 Nov 2016 16:57:50 +0100</pubDate><guid>https://glaforge.dev/talks/2016/11/02/quick-intro-to-gcp-for-the-paris-ansible-meetup/</guid><description>&lt;p>Tonight, Google France was hosting the Paris Ansible meetup, and I had the chance to play the Master of Ceremony, by introducing the speakers for the evening, as well as give a brief introduction to the Google Cloud Platform, as well as outlining where Ansible users and DevOps engineers might be interested in learning more.&lt;/p>
&lt;p>Here&amp;rsquo;s my quick overview of the Google Cloud Platform:&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="b35eb9967bc34da9ad36cc66b988ad57" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script></description><content:encoded>
<![CDATA[<p>Tonight, Google France was hosting the Paris Ansible meetup, and I had the chance to play the Master of Ceremony, by introducing the speakers for the evening, as well as give a brief introduction to the Google Cloud Platform, as well as outlining where Ansible users and DevOps engineers might be interested in learning more.</p>
<p>Here&rsquo;s my quick overview of the Google Cloud Platform:</p>
<script async class="speakerdeck-embed" data-id="b35eb9967bc34da9ad36cc66b988ad57" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Scaling a Swagger-based web API on Google Cloud Endpoints</title><link>https://glaforge.dev/talks/2016/10/27/scaling-a-swagger-based-web-api-on-google-cloud-endpoints/</link><pubDate>Thu, 27 Oct 2016 17:00:56 +0100</pubDate><guid>https://glaforge.dev/talks/2016/10/27/scaling-a-swagger-based-web-api-on-google-cloud-endpoints/</guid><description>&lt;p>I had the pleasure of presenting at the &lt;a href="http://nordicapis.com/events/2016-platform-summit/">Nordic APIs Platform Summit 2016&lt;/a> in Stockholm this week. I enjoyed the conference a lot, with great speakers and content, flawless organization, and nice interactions with the audience.&lt;/p>
&lt;p>For the last keynote of the conference, I had the chance to present about &lt;a href="https://cloud.google.com/endpoints/">Google Cloud Endpoints&lt;/a>, Google&amp;rsquo;s take on API management. I worked on a little &amp;ldquo;pancake&amp;rdquo;-powered demo, deploying a &lt;a href="https://ratpack.io/">Ratpack&lt;/a> application, in a Docker container, on &lt;a href="https://cloud.google.com/container-engine/">Google Container Engine&lt;/a>. I created an &lt;a href="https://openapis.org/">OpenAPI Specification&lt;/a> describing my Web API that served pancakes. And used the Extensible Service Proxy to receive the API calls for securing (with an API key), monitoring (through the Cloud Console) and scaling my Web API (thanks to the scaling capabilities of Container Engine). This demo will be the topic of some upcoming blog posts.&lt;/p></description><content:encoded>
<![CDATA[<p>I had the pleasure of presenting at the <a href="http://nordicapis.com/events/2016-platform-summit/">Nordic APIs Platform Summit 2016</a> in Stockholm this week. I enjoyed the conference a lot, with great speakers and content, flawless organization, and nice interactions with the audience.</p>
<p>For the last keynote of the conference, I had the chance to present about <a href="https://cloud.google.com/endpoints/">Google Cloud Endpoints</a>, Google&rsquo;s take on API management. I worked on a little &ldquo;pancake&rdquo;-powered demo, deploying a <a href="https://ratpack.io/">Ratpack</a> application, in a Docker container, on <a href="https://cloud.google.com/container-engine/">Google Container Engine</a>. I created an <a href="https://openapis.org/">OpenAPI Specification</a> describing my Web API that served pancakes. And used the Extensible Service Proxy to receive the API calls for securing (with an API key), monitoring (through the Cloud Console) and scaling my Web API (thanks to the scaling capabilities of Container Engine). This demo will be the topic of some upcoming blog posts.</p>
<p>In the meantime, here is the abstract of my talk:</p>
<h2 id="scale-a-swagger-based-web-api-with-google-cloud-endpoints">Scale a Swagger-based Web API with Google Cloud Endpoints</h2>
<blockquote>
<p>Web APIs are and more often specified with API definition languages like Swagger (now named Open API Spec), as it can help you generate nice interactive documentation, server skeletons, and client SDKs, mocks, and more, making it simpler to get started both producing and consuming an API.</p>
<p>In this session, Guillaume will demonstrate how to define a Web API with Swagger / Open API Spec, and scale it using Cloud Endpoints, on the Google Cloud Platform.</p></blockquote>
<p>And here are the slides I presented:</p>
<script async class="speakerdeck-embed" data-id="a06c96a7c36a44cba63031f4146b8fae" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Viewing my Groovy source files in Stackdriver's debug view</title><link>https://glaforge.dev/posts/2016/10/17/viewing-my-groovy-source-files-in-stackdriver-debug-view/</link><pubDate>Mon, 17 Oct 2016 17:22:55 +0100</pubDate><guid>https://glaforge.dev/posts/2016/10/17/viewing-my-groovy-source-files-in-stackdriver-debug-view/</guid><description>&lt;p>As I was working on a demo for one of my talks at &lt;a href="https://devoxx.be/">Devoxx&lt;/a>, I was encountering a bug in my &lt;a href="http://www.groovy-lang.org/">Groovy&lt;/a> code (a &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a> app using &lt;a href="http://glide-gae.appspot.com/">Glide&lt;/a>). I had deployed a new version of my &lt;a href="https://cloud.google.com/appengine/">App Engine&lt;/a> app, changing some code to persist some data in the &lt;a href="https://cloud.google.com/datastore/">Datastore&lt;/a>. After those changes, I saw a trace in the logs:&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-af172d2a202d6067b4a0385f0fde4dcb">
&lt;img src="https://glaforge.dev/img/sd-debug-groovy/stackdriver-stacktrace.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-af172d2a202d6067b4a0385f0fde4dcb">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/sd-debug-groovy/stackdriver-stacktrace.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>Looks like there&amp;rsquo;s an error in receiveTweet.groovy on line 11. And there&amp;rsquo;s a link! Although I hadn&amp;rsquo;t linked the source code to the application, I was surprised to see this link. But I knew that Stackdriver is able to &lt;a href="https://cloud.google.com/debugger/docs/source-options">pick up sources&lt;/a> in different ways (from uploaded local files, from a Google code source repository, from Github or BitBucket, or with a &amp;ldquo;source capture&amp;rdquo;).&lt;/p></description><content:encoded>
<![CDATA[<p>As I was working on a demo for one of my talks at <a href="https://devoxx.be/">Devoxx</a>, I was encountering a bug in my <a href="http://www.groovy-lang.org/">Groovy</a> code (a <a href="http://gaelyk.appspot.com/">Gaelyk</a> app using <a href="http://glide-gae.appspot.com/">Glide</a>). I had deployed a new version of my <a href="https://cloud.google.com/appengine/">App Engine</a> app, changing some code to persist some data in the <a href="https://cloud.google.com/datastore/">Datastore</a>. After those changes, I saw a trace in the logs:</p>
<p><figure>
  <a href="#img-af172d2a202d6067b4a0385f0fde4dcb">
    <img src="/img/sd-debug-groovy/stackdriver-stacktrace.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-af172d2a202d6067b4a0385f0fde4dcb">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sd-debug-groovy/stackdriver-stacktrace.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Looks like there&rsquo;s an error in receiveTweet.groovy on line 11. And there&rsquo;s a link! Although I hadn&rsquo;t linked the source code to the application, I was surprised to see this link. But I knew that Stackdriver is able to <a href="https://cloud.google.com/debugger/docs/source-options">pick up sources</a> in different ways (from uploaded local files, from a Google code source repository, from Github or BitBucket, or with a &ldquo;source capture&rdquo;).</p>
<p>And actually, clicking that link brought me to the debug view, offering me the different ways to link to or upload the source code. Conveniently, the source capture approach provided a command, using the <a href="https://cloud.google.com/sdk/gcloud/">gcloud</a> CLI, to link the sources with traces:</p>
<p><figure>
  <a href="#img-fda030e460ba16635c8dd75f741ad3cc">
    <img src="/img/sd-debug-groovy/stackdriver-gcloud-capture.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-fda030e460ba16635c8dd75f741ad3cc">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sd-debug-groovy/stackdriver-gcloud-capture.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I then launched that command in my terminal, and I was able to see the trace along with my source code afterwards in the Web console:</p>
<p><figure>
  <a href="#img-fec04c06ee5d253eb7b7778fbffeda28">
    <img src="/img/sd-debug-groovy/stackdriver-code-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-fec04c06ee5d253eb7b7778fbffeda28">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sd-debug-groovy/stackdriver-code-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>On the left side, I can see my Groovy source files, highlighting the offending script with the bug. In the middle column: at the bottom, the stacktrace, and at the top, the source code, with the line where the exception occurred highlighted in blue.</p>
<p>On the right, there&rsquo;s also the live debugger view! But I haven&rsquo;t played with it yet, but it&rsquo;s pretty powerful, as you can live debug a production app! Let&rsquo;s keep it for another post!</p>
<p>However, now with your Groovy hat on, you&rsquo;ll notice two things:</p>
<p><figure>
  <a href="#img-b463c5e1b47722061ae6cfcbd04dad18">
    <img src="/img/sd-debug-groovy/stackdriver-zoom-code.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b463c5e1b47722061ae6cfcbd04dad18">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sd-debug-groovy/stackdriver-zoom-code.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The Groovy source code is not nicely colored! Syntax coloring is available for languages like Java, Go, Python, JavaScript, but alas, not (yet?) for Groovy!</p>
<p>The other funny thing was the red message on the right as well:</p>
<p><figure>
  <a href="#img-95a808a3f1caa27833525d53a9552a89">
    <img src="/img/sd-debug-groovy/stackdriver-red-message.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-95a808a3f1caa27833525d53a9552a89">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/sd-debug-groovy/stackdriver-red-message.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Although it says only files with .java extension are supported, it was nice to see that it was still showing my Groovy source code!</p>
<h2 id="summary">Summary</h2>
<p>It&rsquo;s pretty neat to be able to associate the code and the logs directly in the web interface, to quickly spot where problems are coming from. Of course, you can go back to your IDE or text editor to find out (and ultimately that&rsquo;s what you&rsquo;ll be doing) but it&rsquo;s pretty handy to quickly visualize the origin of the problem and start figuring out what the problem may be.</p>
<p>Also, as I said, I haven&rsquo;t tried the live production debugger, but it&rsquo;s quite a killer feature in my book to be able to introspect a running app. It&rsquo;s not always easy to figure out some problems locally, as your emulator is not the actually running infrastructure, your tests are mocking things out but are &ldquo;not like the real thing&rdquo;, so having the ability to dive deeper in the running system is pretty compelling!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>IP filtering access to your VMs on Google Cloud</title><link>https://glaforge.dev/posts/2016/10/03/ip-filtering-access-to-your-vms-on-google-cloud/</link><pubDate>Mon, 03 Oct 2016 17:25:56 +0100</pubDate><guid>https://glaforge.dev/posts/2016/10/03/ip-filtering-access-to-your-vms-on-google-cloud/</guid><description>&lt;p>How do you filter access to your VMs on Google Cloud Platform? During a discussion with a customer, I was asked this question: only certain IP addresses or a range of IP addresses should have access to a particular VM. Let&amp;rsquo;s see that in action!&lt;/p>
&lt;p>Let&amp;rsquo;s assume you already have an account on Google Cloud Platform, but if you don&amp;rsquo;t, don&amp;rsquo;t miss the &lt;a href="https://cloud.google.com/free-trial/">$300 credits for a free trial&lt;/a>! I created a new project, then navigated to the Compute Engine section to create a new VM instance. I used all the default parameters, except that I checked the checkbox for &amp;ldquo;Allow HTTP traffic&amp;rdquo;, at the bottom of the following screenshot:&lt;/p></description><content:encoded>
<![CDATA[<p>How do you filter access to your VMs on Google Cloud Platform? During a discussion with a customer, I was asked this question: only certain IP addresses or a range of IP addresses should have access to a particular VM. Let&rsquo;s see that in action!</p>
<p>Let&rsquo;s assume you already have an account on Google Cloud Platform, but if you don&rsquo;t, don&rsquo;t miss the <a href="https://cloud.google.com/free-trial/">$300 credits for a free trial</a>! I created a new project, then navigated to the Compute Engine section to create a new VM instance. I used all the default parameters, except that I checked the checkbox for &ldquo;Allow HTTP traffic&rdquo;, at the bottom of the following screenshot:</p>
<p><figure>
  <a href="#img-2ba8746532d6b70254ced66ae4425397">
    <img src="/img/misc/firewall-create-instance.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-2ba8746532d6b70254ced66ae4425397">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/firewall-create-instance.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>For the purpose of this demo, I went with allowing traffic first, and then updating the firewall rule, but the best approach (since you don&rsquo;t want to let users access this VM) is to not allow HTTP traffic, and add the right rule afterwards. But I wanted to check that the traffic was flowing through normally, and then updated the rule to check that, indeed, the traffic was filtered.</p>
<p>My VM server isn&rsquo;t doing anything useful at this point, so I should at least run some web app on it! Wearing my <a href="http://www.groovy-lang.org/">Groovy</a> hat on, I decided to write a quick Groovy script with the <a href="https://ratpack.io/">Ratpack</a> framework. Let&rsquo;s see how to setup our VM to serve a simple hello world!</p>
<p>Once your VM instance is instantiated, you&rsquo;ll see a little SSH link along your instance in the list of running VMs. You can click on it, and you&rsquo;ll be able to SSH into your running system. So what&rsquo;s the recipe to run a little hello world in Ratpack? I installed OpenJDK 8, <a href="http://sdkman.io/">SDKMan</a> (to install Groovy, but which needed unzip to be installed for itself), and Groovy, with the following steps:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo su -
</span></span><span style="display:flex;"><span>apt-get update
</span></span><span style="display:flex;"><span>apt-get install openjdk-8-jdk
</span></span><span style="display:flex;"><span>apt-get install unzip
</span></span><span style="display:flex;"><span>curl -s <span style="color:#4070a0">&#34;https://get.sdkman.io&#34;</span> | bash
</span></span><span style="display:flex;"><span><span style="color:#007020">source</span> <span style="color:#4070a0">&#34;/root/.sdkman/bin/sdkman-init.sh&#34;</span>
</span></span><span style="display:flex;"><span>sdk install groovy
</span></span><span style="display:flex;"><span><span style="color:#007020">exit</span>
</span></span><span style="display:flex;"><span>mkdir ratpack
</span></span><span style="display:flex;"><span><span style="color:#007020">cd</span> ratpack
</span></span><span style="display:flex;"><span>vim hello.groovy
</span></span></code></pre></div><p>Then I created the hello.groovy Ratpack server with the following code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;io.ratpack:ratpack-groovy:1.4.2&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;org.slf4j:slf4j-simple:1.7.21&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">static</span> ratpack<span style="color:#666">.</span><span style="color:#4070a0">groovy</span><span style="color:#666">.</span><span style="color:#4070a0">Groovy</span><span style="color:#666">.</span><span style="color:#4070a0">ratpack</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ratpack <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    serverConfig <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        port <span style="color:#40a070">80</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    handlers <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        get <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            render <span style="color:#4070a0">&#34;Hello World!&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>And then, I was ready to fire it with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>groovy hello
</span></span></code></pre></div><p>If you go back to your Google Cloud console, in the list of running instance, you certainly noticed the column showing the &ldquo;External IP&rdquo; address of your sever? Now you just need to let your browser open it. So head over to <code>http://123.123.123.123/</code> (or whichever IP you got), and you should see the infamous <code>Hello World!</code> message!</p>
<p>So far so good, but what we really want is to prevent access to this machine from anywhere, except a particular IP or range of IP addresses. Let&rsquo;s see how to do that next.</p>
<p>Let&rsquo;s go to the <code>Networking &gt; Firewall rules</code>:</p>
<p><figure>
  <a href="#img-02add3446704aaf99aa700965ede587f">
    <img src="/img/ip-filter/firewall-edit-rule.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-02add3446704aaf99aa700965ede587f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/ip-filter/firewall-edit-rule.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>We&rsquo;re going to update the first rule: <code>default-allow-http</code>. Instead of <code>allowing from any source</code> with the <code>0.0.0.0/0</code> IP range, we&rsquo;re going to use our own custom range. In our case, let&rsquo;s say my own external IP address is <code>111.111.111.111</code>, so I&rsquo;ll restrict the range to just this IP with entering <code>111.111.111.111/0</code> as <code>Source IP range</code>. Let&rsquo;s save the firewall, and let the platform apply that change to our deployment. Once the change has taken place, you&rsquo;ll still be able to access your server at <code>http://123.123.123.123/</code>, because only your own IP address is white listed basically. But if you try with any other address (from a co-worker&rsquo;s machine, etc.), normally, nobody else will be able to access the sever beside you.</p>
<p>Done!</p>
<p>And now, for the bonus points! I started playing with that last week, and made the mistake of letting my VM instance running, underutilized. And this afternoon, as I resumed working on this article, I watch the list of instances running, and what do I see? The console telling me my VM instance is under-utilized and that I could save money by using a smaller VM instead! Looks like Google Cloud doesn&rsquo;t want me to waste my money! Sweet!</p>
<p><figure>
  <a href="#img-1365257bb9c659d824b04781f7cd3499">
    <img src="/img/ip-filter/firewall-save-cost.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1365257bb9c659d824b04781f7cd3499">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/ip-filter/firewall-save-cost.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>GCloud informative update message</title><link>https://glaforge.dev/posts/2016/09/28/gcloud-informative-update-message/</link><pubDate>Wed, 28 Sep 2016 17:30:28 +0100</pubDate><guid>https://glaforge.dev/posts/2016/09/28/gcloud-informative-update-message/</guid><description>&lt;p>I was playing with the &lt;a href="https://cloudplatform.googleblog.com/2016/08/never-leave-your-Java-IDE-with-Google-Cloud-Tools-for-IntelliJ-plugin.html">new IntelliJ IDEA plugin for Google App Engine&lt;/a> yesterday. The plugin depends on the &lt;a href="https://cloud.google.com/sdk/gcloud/">gcloud SDK&lt;/a> to do its work. And I started exploring gcloud a little bit more.&lt;/p>
&lt;p>I was experiencing some odd bug which prevented me to run my application locally with the App Engine&amp;rsquo;s local app server. It was a bug which was present in an old version of gcloud and its App Engine component, so I had to update the SDK and its App Engine Java component to fix it. No big deal, but what I wanted to highlight here was a little detail about that upgrade process.&lt;/p></description><content:encoded>
<![CDATA[<p>I was playing with the <a href="https://cloudplatform.googleblog.com/2016/08/never-leave-your-Java-IDE-with-Google-Cloud-Tools-for-IntelliJ-plugin.html">new IntelliJ IDEA plugin for Google App Engine</a> yesterday. The plugin depends on the <a href="https://cloud.google.com/sdk/gcloud/">gcloud SDK</a> to do its work. And I started exploring gcloud a little bit more.</p>
<p>I was experiencing some odd bug which prevented me to run my application locally with the App Engine&rsquo;s local app server. It was a bug which was present in an old version of gcloud and its App Engine component, so I had to update the SDK and its App Engine Java component to fix it. No big deal, but what I wanted to highlight here was a little detail about that upgrade process.</p>
<p>I love when SDKs give me information about what needs updating and what&rsquo;s new in the new versions I&rsquo;m using!</p>
<p>I&rsquo;ve been using <a href="http://sdkman.io/">SDKMan</a> for dealing with various SDK installations, like those for Groovy, Grails, Gradle, etc, and I&rsquo;ve always liked when it was telling me which SDK updates were available, what was new in SDKMan. And I&rsquo;m glad to see that gcloud behaves the same, and gives informative details about what&rsquo;s new. So let&rsquo;s see that in action.</p>
<p>First of all, while debugging my problem with a colleague, he asked me which versions of the SDK and the App Engine component I had. So I ran the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud version
</span></span><span style="display:flex;"><span>Google Cloud SDK 119.0.0
</span></span><span style="display:flex;"><span>alpha 2016.01.12
</span></span><span style="display:flex;"><span>app-engine-java 1.9.38
</span></span><span style="display:flex;"><span>app-engine-python 1.9.38
</span></span><span style="display:flex;"><span>beta 2016.01.12
</span></span><span style="display:flex;"><span>bq 2.0.24
</span></span><span style="display:flex;"><span>bq-nix 2.0.24
</span></span><span style="display:flex;"><span>core 2016.07.21
</span></span><span style="display:flex;"><span>core-nix 2016.06.06
</span></span><span style="display:flex;"><span>gcloud 
</span></span><span style="display:flex;"><span>gsutil 4.19
</span></span><span style="display:flex;"><span>gsutil-nix 4.19
</span></span></code></pre></div><p>At the time of this writing, the latest version of gcloud was actually 127.0.0, but I had 119.0.0. And for the app-engine-java component, I had version 1.9.38 although 1.9.42 was available. So it was time to update!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ gcloud components update
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Your current Cloud SDK version is: 119.0.0
</span></span><span style="display:flex;"><span>You will be upgraded to version: 127.0.0
</span></span><span style="display:flex;"><span>┌──────────────────────────────────────────────────────────┐
</span></span><span style="display:flex;"><span>│            These components will be updated.             │
</span></span><span style="display:flex;"><span>├─────────────────────────────────┬────────────┬───────────┤
</span></span><span style="display:flex;"><span>│               Name              │  Version   │    Size   │
</span></span><span style="display:flex;"><span>├─────────────────────────────────┼────────────┼───────────┤
</span></span><span style="display:flex;"><span>│ BigQuery Command Line Tool      │     2.0.24 │   &lt; <span style="color:#40a070">1</span> MiB │
</span></span><span style="display:flex;"><span>│ Cloud SDK Core Libraries        │ 2016.09.20 │   4.9 MiB │
</span></span><span style="display:flex;"><span>│ Cloud Storage Command Line Tool │       4.21 │   2.8 MiB │
</span></span><span style="display:flex;"><span>│ gcloud app Java Extensions      │     1.9.42 │ 135.6 MiB │
</span></span><span style="display:flex;"><span>│ gcloud app Python Extensions    │     1.9.40 │   7.2 MiB │
</span></span><span style="display:flex;"><span>└─────────────────────────────────┴────────────┴───────────┘
</span></span><span style="display:flex;"><span>The following release notes are new in this upgrade.
</span></span><span style="display:flex;"><span>Please <span style="color:#007020">read</span> carefully <span style="color:#007020;font-weight:bold">for</span> information about new features, breaking changes,
</span></span><span style="display:flex;"><span>and bugs fixed.  The latest full release notes can be viewed at:
</span></span><span style="display:flex;"><span>  https://cloud.google.com/sdk/release_notes
</span></span><span style="display:flex;"><span>127.0.0 <span style="color:#666">(</span>2016/09/21<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>  Google BigQuery
</span></span><span style="display:flex;"><span>      - New load/query option in BigQuery client to support schema update
</span></span><span style="display:flex;"><span>        within a load/query job.
</span></span><span style="display:flex;"><span>      - New query option in BigQuery client to specify query parameters in
</span></span><span style="display:flex;"><span>        Standard SQL.
</span></span><span style="display:flex;"><span>  Google Cloud Dataproc
</span></span><span style="display:flex;"><span>      - gcloud dataproc clusters create flag
</span></span><span style="display:flex;"><span>        --preemptible-worker-boot-disk-size can be used to specify future
</span></span><span style="display:flex;"><span>        preemptible VM boot disk size.
</span></span><span style="display:flex;"><span>  Google Container Engine
</span></span><span style="display:flex;"><span>      - Update kubectl to version 1.3.7.
</span></span><span style="display:flex;"><span>  Google Cloud ML
</span></span><span style="display:flex;"><span>      - New gcloud beta ml predict <span style="color:#007020">command</span> to <span style="color:#007020;font-weight:bold">do</span> online prediction.
</span></span><span style="display:flex;"><span>      - New gcloud beta ml <span style="color:#007020">jobs</span> submit prediction <span style="color:#007020">command</span> to submit batch
</span></span><span style="display:flex;"><span>        prediction job.
</span></span><span style="display:flex;"><span>  Google Cloud SQL
</span></span><span style="display:flex;"><span>      - New arguments to beta sql instances create/patch commands <span style="color:#007020;font-weight:bold">for</span> Cloud
</span></span><span style="display:flex;"><span>        SQL Second Generation instances:
</span></span><span style="display:flex;"><span>        ◆ --storage-size Sets storage size in GB.
</span></span><span style="display:flex;"><span>        ◆ --maintenance-release-channel Sets production or preview channel
</span></span><span style="display:flex;"><span>          <span style="color:#007020;font-weight:bold">for</span> maintenance window.
</span></span><span style="display:flex;"><span>        ◆ --maintenance-window-day Sets day of week <span style="color:#007020;font-weight:bold">for</span> maintenance window.
</span></span><span style="display:flex;"><span>        ◆ --maintenance-window-hour Sets hour of day <span style="color:#007020;font-weight:bold">for</span> maintenance window.
</span></span><span style="display:flex;"><span>        ◆ --maintenance-window-any <span style="color:#666">(</span>patch only<span style="color:#666">)</span> Clears maintenance window
</span></span><span style="display:flex;"><span>          setting.
</span></span><span style="display:flex;"><span><span style="color:#666">[</span>...<span style="color:#666">]</span>
</span></span></code></pre></div><p>I snipped the output to just show the details of the changes for the latest version of gcloud, but it showed me the actual changelog up until the version I had&hellip; and as I hadn&rsquo;t updated in a while, there was lots of improvements and fixes! But it&rsquo;s really nice to see what had changed, and sometimes, you can discover some gems you weren&rsquo;t even aware of!</p>
<p>So if you&rsquo;re working on some kind of SDK, with auto-update capabilities, be sure to provide a built-in changelog facility to help your users know what&rsquo;s new and improved!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>JavaOne 2016 sessions</title><link>https://glaforge.dev/posts/2016/09/14/javaone-2016-sessions/</link><pubDate>Wed, 14 Sep 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/09/14/javaone-2016-sessions/</guid><description>&lt;p>Next week will be this time of the year where tons of Java developers are gathering &amp;amp; meeting in San Francisco for &lt;a href="https://www.oracle.com/javaone/index.html">JavaOne&lt;/a>. It&amp;rsquo;ll be my 10th edition or so, time flies!&lt;br />
This year, I&amp;rsquo;ll participate to a couple sessions:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Java and the Commoditization of Machine Intelligence&lt;/strong> [CON2291]&lt;br />
It&amp;rsquo;s a panel discussion with representative from IBM, Microsoft and Google to talk about Machine Learning APIs. I&amp;rsquo;ll be covering the ML APIs from Google Cloud Platform: Vision, Speech, Natural Language.&lt;/li>
&lt;li>&lt;strong>A Groovy Journey in Open Source&lt;/strong> [CON5932]&lt;br />
In this session, I&amp;rsquo;ll cover the history of the Apache Groovy project, and talk about the latest developments and new features.&lt;/li>
&lt;/ul>
&lt;p>Google colleagues will also be present to speak about:&lt;/p></description><content:encoded>
<![CDATA[<p>Next week will be this time of the year where tons of Java developers are gathering &amp; meeting in San Francisco for <a href="https://www.oracle.com/javaone/index.html">JavaOne</a>. It&rsquo;ll be my 10th edition or so, time flies!<br />
This year, I&rsquo;ll participate to a couple sessions:</p>
<ul>
<li><strong>Java and the Commoditization of Machine Intelligence</strong> [CON2291]<br />
It&rsquo;s a panel discussion with representative from IBM, Microsoft and Google to talk about Machine Learning APIs. I&rsquo;ll be covering the ML APIs from Google Cloud Platform: Vision, Speech, Natural Language.</li>
<li><strong>A Groovy Journey in Open Source</strong> [CON5932]<br />
In this session, I&rsquo;ll cover the history of the Apache Groovy project, and talk about the latest developments and new features.</li>
</ul>
<p>Google colleagues will also be present to speak about:</p>
<ul>
<li><strong>gRPC 101 for Java Developers</strong> [CON5750] by Ray Tsang</li>
<li><strong>Managing and Deploying Java-Based Applications and Services at Scale</strong> [CON5730] by Ray Tsang</li>
<li><strong>Hacking Hiring</strong> [BOF1459] by Elliotte Harold</li>
<li><strong>The Ultimate Build Tools Face-off</strong> [CON2270] with Dmitry Churbanau and Baruch Sadogursky</li>
<li><strong>RIA Technologies and Frameworks Panel</strong> [CON4675] with Kevin Nilson</li>
</ul>
<p>There are quite a few interesting Groovy ecosystem related talks on the agenda:</p>
<ul>
<li><strong>Improving Your Groovy Kung-Fu</strong> [CON1293] by Dierk König</li>
<li><strong>Groovy and Java 8: Making Java Better</strong> [CON3277] by Ken Kousen</li>
<li><strong>Spock: Test Well and Prosper</strong> [CON3273] by Ken Kousen</li>
<li><strong>Writing Groovy AST Transformations: Getting Practical in an Hour</strong> [CON1238] by Baruch Sadogursky</li>
<li><strong>Juggling Multiple Java Platforms and Jigsaw with Gradle</strong> [CON4832] by Cédric Champeau</li>
<li><strong>Maven Versus Gradle: Ready&hellip;Steady&hellip;Go!</strong> [CON2951] by Mert Caliskan &amp; Murat Yener</li>
<li><strong>Meet the Gradle Team</strong> [BOF6209] with Sterling Greene &amp; Cédric Champeau</li>
<li><strong>Faster Java EE Builds with Gradle</strong> [CON4921] by Ryan Cuprak</li>
<li><strong>Lightweight Developer Provisioning with Gradle</strong> [BOF5154] by Mario-Leander Reimer</li>
<li><strong>Making the Most of Your Gradle Build</strong> [CON6468] by Andrés Almiray</li>
<li><strong>Gradle Support in NetBeans: A State of the Union</strong> [CON6253] with Sven Reimers &amp; Martin Klähn</li>
<li><strong>A Practical RxJava Example with Ratpack</strong> [CON4044] by Laurent Doguin</li>
</ul>
<p>Lots of interesting content! I&rsquo;m really looking forward to meeting you there, in the hallways, to chat about <a href="https://cloud.google.com/">Google Cloud Platform</a> and <a href="http://www.groovy-lang.org/">Apache Groovy</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Natural language API and JavaScript promises to bind them all</title><link>https://glaforge.dev/posts/2016/07/28/natural-language-api-and-javascript-promises-to-bind-them-all/</link><pubDate>Thu, 28 Jul 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/07/28/natural-language-api-and-javascript-promises-to-bind-them-all/</guid><description>&lt;p>A bit of &lt;a href="https://glaforge.dev/posts/2016/07/27/web-scraping-and-rest-api-calls-on-app-engine-with-jsoup-and-groovy-wslite/">web scraping with Jsoup and REST API calls with groovy-wsclient&lt;/a> helped me build my latest demo with &lt;a href="http://glide-gae.appspot.com/">Glide&lt;/a> / &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a> on &lt;a href="https://cloud.google.com/appengine/">App Engine&lt;/a>, but now, it&amp;rsquo;s time to look a bit deeper into the analysis of the White House speeches:&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-90d80696f1e24c7a793b415cb46126da">
&lt;img src="https://glaforge.dev/img/misc/whitehouse-speeches-630.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-90d80696f1e24c7a793b415cb46126da">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/whitehouse-speeches-630.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>I wanted to have a feel of how positive and negative sentences flow together in speeches. Looking at the rhetoric of those texts, you&amp;rsquo;d find some flows of generally neutral introduction, then posing the problem with some negativity connotation, then the climax trying to unfold the problems with positive solutions. Some other topics might be totally different, though, but I was curious to see how this played out on the corpus of texts from the speeches and remarks published by the &lt;a href="https://www.whitehouse.gov/briefing-room/speeches-and-remarks">White House press office&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>A bit of <a href="https://glaforge.dev/posts/2016/07/27/web-scraping-and-rest-api-calls-on-app-engine-with-jsoup-and-groovy-wslite/">web scraping with Jsoup and REST API calls with groovy-wsclient</a> helped me build my latest demo with <a href="http://glide-gae.appspot.com/">Glide</a> / <a href="http://gaelyk.appspot.com/">Gaelyk</a> on <a href="https://cloud.google.com/appengine/">App Engine</a>, but now, it&rsquo;s time to look a bit deeper into the analysis of the White House speeches:</p>
<p><figure>
  <a href="#img-90d80696f1e24c7a793b415cb46126da">
    <img src="/img/misc/whitehouse-speeches-630.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-90d80696f1e24c7a793b415cb46126da">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/whitehouse-speeches-630.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I wanted to have a feel of how positive and negative sentences flow together in speeches. Looking at the rhetoric of those texts, you&rsquo;d find some flows of generally neutral introduction, then posing the problem with some negativity connotation, then the climax trying to unfold the problems with positive solutions. Some other topics might be totally different, though, but I was curious to see how this played out on the corpus of texts from the speeches and remarks published by the <a href="https://www.whitehouse.gov/briefing-room/speeches-and-remarks">White House press office</a>.</p>
<h2 id="the-cloud-natural-language-api">The Cloud Natural Language API</h2>
<p>For that purpose, I used the <a href="https://cloud.google.com/natural-language/docs/">Cloud Natural Language API</a>:</p>
<ul>
<li><a href="https://cloud.google.com/natural-language/docs/basics#sentence-extraction">Split the text into sentences</a> thanks to the <a href="https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText">text annotation</a> capability. The API can split sentences even further, of course, by word, to figure out verbs, nouns, and all components of sentences (POS: Part Of Speech tagging).</li>
<li>Define the <a href="https://cloud.google.com/natural-language/docs/sentiment-tutorial">sentiment</a> of sentences, with a polarity (negative to positive), and a magnitude (for the intensity of the sentiment expressed).</li>
<li><a href="https://cloud.google.com/natural-language/docs/basics#entity_analysis">Extract entities</a>, ie. finding people / organization / enterprise names, place locations, etc.</li>
</ul>
<p>Text annotation is important for better understanding text, for example to create more accurate language translations. Sentiment analysis can help brands track how their customers appreciate their products. And entity extraction can help figure out the topics of articles, who&rsquo;s mentioned, places where the action takes places, which is useful for further contextual search, like finding all the articles about Obama, all the speeches about Europe, etc. There&rsquo;s a wide applicability of those various services to provide more metadata, a better understanding for a given piece of text.</p>
<h2 id="asynchronously-calling-the-service-and-gathering-results">Asynchronously calling the service and gathering results</h2>
<p>Let&rsquo;s look back at my experiment. When I scrape the speeches, I actually get a list of paragraphs (initially enclosed in &lt;p&gt; tags basically). But I want to analyze the text sentence by sentence, so I need to use the text annotation capability to split all those paragraphs into sentences that I analyze individually.</p>
<p>Currently, the sentiment analysis works on one piece of text at a time. So you have to make one call per sentence! Hopefully an option might come to allow to send several pieces of text in a batch, or giving the sentiment per sentence for a big chunk of text, etc. But for now, it means I&rsquo;ll have to make p calls for my p paragraphs, and then n calls for all the sentences. those p + n calls might be expensive in terms of network traffic, but on the other hand, I can make the sentence coloring appear progressively, and asynchronously, by using JavaScript Promises and Fetch API, as I&rsquo;m making those calls from the client side. But it seems it&rsquo;s possible to <a href="https://developers.google.com/api-client-library/java/google-api-java-client/batch">batch requests with the Google API Client</a>, but I haven&rsquo;t tried that yet.</p>
<p>First of all, to simplify the code a bit, I&rsquo;ve created a helper function that calls my backend services calling the NL API, that wraps the usage of the Fetch API, and the promise handling to gather the JSON response:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span> callService <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">function</span> (url, key, value) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">var</span> query <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> URLSearchParams();
</span></span><span style="display:flex;"><span>    query.append(key, value);
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">return</span> fetch(url, {
</span></span><span style="display:flex;"><span>        method<span style="color:#666">:</span> <span style="color:#4070a0">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>        body<span style="color:#666">:</span> query
</span></span><span style="display:flex;"><span>    }).then(<span style="color:#007020;font-weight:bold">function</span> (resp) {
</span></span><span style="display:flex;"><span>        <span style="color:#007020;font-weight:bold">return</span> resp.json();
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>I use the URLSearchParams object to pass my query parameter. The handy json() method on the response gives me the data structure resulting from the call. I&rsquo;m going to reuse that callService function in the following snippets:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>callService(<span style="color:#4070a0">&#39;/content&#39;</span>, <span style="color:#4070a0">&#39;url&#39;</span>, e.value).then(<span style="color:#007020;font-weight:bold">function</span> (paragraphs) {
</span></span><span style="display:flex;"><span>    paragraphs.forEach(<span style="color:#007020;font-weight:bold">function</span> (para, paraIdx) {
</span></span><span style="display:flex;"><span>        z(<span style="color:#4070a0">&#39;#output&#39;</span>).append(<span style="color:#4070a0">&#39;&lt;p id=&#34;para&#39;</span> <span style="color:#666">+</span> paraIdx <span style="color:#666">+</span> <span style="color:#4070a0">&#39;&#34;&gt;&#39;</span> <span style="color:#666">+</span> para <span style="color:#666">+</span> <span style="color:#4070a0">&#39;&lt;/p&gt;&#39;</span>);
</span></span><span style="display:flex;"><span>        callService(<span style="color:#4070a0">&#39;/sentences&#39;</span>, <span style="color:#4070a0">&#39;content&#39;</span>, para).then(<span style="color:#007020;font-weight:bold">function</span> (data) {
</span></span><span style="display:flex;"><span>            <span style="color:#007020;font-weight:bold">var</span> sentences <span style="color:#666">=</span> data.sentences.map(<span style="color:#007020;font-weight:bold">function</span> (sentence) {
</span></span><span style="display:flex;"><span>                <span style="color:#007020;font-weight:bold">return</span> sentence.text.content;
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>            <span style="color:#007020;font-weight:bold">return</span> <span style="color:#007020">Promise</span>.all(sentences.map(<span style="color:#007020;font-weight:bold">function</span> (sentence) {
</span></span><span style="display:flex;"><span>                <span style="color:#007020;font-weight:bold">return</span> callService(<span style="color:#4070a0">&#39;/sentence&#39;</span>, <span style="color:#4070a0">&#39;content&#39;</span>, sentence).then(<span style="color:#007020;font-weight:bold">function</span> (sentenceSentiment) {
</span></span><span style="display:flex;"><span>                    <span style="color:#007020;font-weight:bold">var</span> polarity <span style="color:#666">=</span> sentenceSentiment.documentSentiment.polarity;
</span></span><span style="display:flex;"><span>                    <span style="color:#007020;font-weight:bold">var</span> magnitude <span style="color:#666">=</span> sentenceSentiment.documentSentiment.magnitude;
</span></span><span style="display:flex;"><span>                    <span style="color:#007020;font-weight:bold">return</span> {
</span></span><span style="display:flex;"><span>                        sentence<span style="color:#666">:</span> sentence,
</span></span><span style="display:flex;"><span>                        polarity<span style="color:#666">:</span> polarity,
</span></span><span style="display:flex;"><span>                        magnitude<span style="color:#666">:</span> magnitude
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                });
</span></span><span style="display:flex;"><span>            }));
</span></span><span style="display:flex;"><span>        }).then(<span style="color:#007020;font-weight:bold">function</span> (allSentiments) {
</span></span><span style="display:flex;"><span>            <span style="color:#007020;font-weight:bold">var</span> coloredSentences <span style="color:#666">=</span> allSentiments.map(<span style="color:#007020;font-weight:bold">function</span> (sentiment) {
</span></span><span style="display:flex;"><span>                <span style="color:#007020;font-weight:bold">var</span> hsl <span style="color:#666">=</span> <span style="color:#4070a0">&#39;hsl(&#39;</span> <span style="color:#666">+</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#007020">Math</span>.floor((sentiment.polarity <span style="color:#666">+</span> <span style="color:#40a070">1</span>) <span style="color:#666">*</span> <span style="color:#40a070">60</span>) <span style="color:#666">+</span> <span style="color:#4070a0">&#39;, &#39;</span> <span style="color:#666">+</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#007020">Math</span>.min(<span style="color:#007020">Math</span>.floor(sentiment.magnitude <span style="color:#666">*</span> <span style="color:#40a070">100</span>), <span style="color:#40a070">100</span>) <span style="color:#666">+</span> <span style="color:#4070a0">&#39;%, &#39;</span> <span style="color:#666">+</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#4070a0">&#39;90%) !important&#39;</span>;
</span></span><span style="display:flex;"><span>                <span style="color:#007020;font-weight:bold">return</span> <span style="color:#4070a0">&#39;&lt;span style=&#34;background-color: &#39;</span> <span style="color:#666">+</span> hsl <span style="color:#666">+</span> <span style="color:#4070a0">&#39;&#34;&gt;&#39;</span> <span style="color:#666">+</span> sentiment.sentence <span style="color:#666">+</span> <span style="color:#4070a0">&#39;&lt;/span&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>            }).join(<span style="color:#4070a0">&#39;&amp;nbsp;&amp;nbsp;&#39;</span>);
</span></span><span style="display:flex;"><span>            z(<span style="color:#4070a0">&#39;#para&#39;</span> <span style="color:#666">+</span> paraIdx).html(coloredSentences);
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>The first call will fetch the paragraphs from the web scraping service. I display each paragraph right away, uncolored, with an id so that I can then later update each paragraph with colored sentences with their sentiment.</p>
<p>Now for each paragraph, I call the sentences service, which calls the NL API to get the individual sentences of each paragraph. With all the sentences in one go, I use the Promise.all(iterable) method which returns a promise that resolves when all the promises of sentiment analysis per sentence have resolved. This will help me keep track of the order of sentences, as the analysis can give me results in a non predictable order.</p>
<p>I also keep track of the paragraph index to replace all the sentences of each paragraph, once all the promises for the sentences are resolved. I update the paragraph with colored sentences once all sentences of a paragraph are resolved, joining all colored sentences together.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Web scraping and REST API calls on App Engine with Jsoup and groovy-wslite</title><link>https://glaforge.dev/posts/2016/07/27/web-scraping-and-rest-api-calls-on-app-engine-with-jsoup-and-groovy-wslite/</link><pubDate>Wed, 27 Jul 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/07/27/web-scraping-and-rest-api-calls-on-app-engine-with-jsoup-and-groovy-wslite/</guid><description>&lt;p>After my &lt;a href="https://glaforge.dev/posts/2016/07/20/sentiment-analysis-on-tweets/">Twitter sentiment article&lt;/a>, those past couple of days, I&amp;rsquo;ve been playing again with the &lt;a href="https://cloud.google.com/natural-language/docs/">Cloud Natural Language API&lt;/a>. This time, I wanted to make a little demo analyzing the text of speeches and remarks published by the press office of the White House. It&amp;rsquo;s interesting to see how speeches alternate negative and positive sequences, to reinforce the argument being exposed.&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-90d80696f1e24c7a793b415cb46126da">
&lt;img src="https://glaforge.dev/img/misc/whitehouse-speeches-630.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-90d80696f1e24c7a793b415cb46126da">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/whitehouse-speeches-630.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>As usual, for my cloud demos, my weapons of choice for rapid development are &lt;a href="http://www.groovy-lang.org/">Apache Groovy&lt;/a>, with &lt;a href="http://glide-gae.appspot.com/">Glide&lt;/a> &amp;amp; &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a> on &lt;a href="https://cloud.google.com/appengine/">Google App Engine&lt;/a>! But for this demo, I needed two things:&lt;/p></description><content:encoded>
<![CDATA[<p>After my <a href="https://glaforge.dev/posts/2016/07/20/sentiment-analysis-on-tweets/">Twitter sentiment article</a>, those past couple of days, I&rsquo;ve been playing again with the <a href="https://cloud.google.com/natural-language/docs/">Cloud Natural Language API</a>. This time, I wanted to make a little demo analyzing the text of speeches and remarks published by the press office of the White House. It&rsquo;s interesting to see how speeches alternate negative and positive sequences, to reinforce the argument being exposed.</p>
<p><figure>
  <a href="#img-90d80696f1e24c7a793b415cb46126da">
    <img src="/img/misc/whitehouse-speeches-630.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-90d80696f1e24c7a793b415cb46126da">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/whitehouse-speeches-630.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>As usual, for my cloud demos, my weapons of choice for rapid development are <a href="http://www.groovy-lang.org/">Apache Groovy</a>, with <a href="http://glide-gae.appspot.com/">Glide</a> &amp; <a href="http://gaelyk.appspot.com/">Gaelyk</a> on <a href="https://cloud.google.com/appengine/">Google App Engine</a>! But for this demo, I needed two things:</p>
<ul>
<li>a way to scrape the content of the <a href="https://www.whitehouse.gov/briefing-room/speeches-and-remarks">speeches &amp; remarks</a> from the White House press office</li>
<li>a library for easily making REST calls to the <a href="https://cloud.google.com/natural-language/docs/">Natural Language API</a></li>
</ul>
<p>In both cases, we need to issue calls through the internet, and there are some limitations on App Engine with regards to such <a href="https://cloud.google.com/appengine/docs/java/outbound-requests">outbound networking</a>. But if you use the plain Java HTTP / URL networking classes, you are fine. And under the hood, it&rsquo;s using App Engine&rsquo;s own <a href="https://cloud.google.com/appengine/docs/java/issue-requests">URL Fetch service</a>.<br />
I used <a href="https://jsoup.org/">Jsoup</a> for web scraping, which takes care itself for connecting to the web site.<br />
For interacting with the REST API, <a href="https://github.com/jwagenleitner/groovy-wslite">groovy-wslight</a> came to my rescue, although I could have used the Java SDK like in my previous article.<br />
Let&rsquo;s look at Jsoup and scraping first. In my controller fetching the content, I did something along those lines (you can run this script in the Groovy console):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;org.jsoup:jsoup:1.9.2&#39;</span><span style="color:#666">)</span> <span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">org.jsoup.</span><span style="">\</span><span style="color:#666">*</span>   
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> url <span style="color:#666">=</span> <span style="color:#4070a0">&#39;https://www.whitehouse.gov/the-press-office/2016/07/17/statement-president-shootings-baton-rouge-louisiana&#39;</span> <span style="color:#902000">def</span> doc <span style="color:#666">=</span> Jsoup<span style="color:#666">.</span><span style="color:#4070a0">connect</span><span style="color:#666">(</span>url<span style="color:#666">)</span> <span style="color:#666">.</span><span style="color:#4070a0">userAgent</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0&#39;</span><span style="color:#666">)</span> <span style="color:#666">.</span><span style="color:#4070a0">get</span><span style="color:#666">()</span> println doc<span style="color:#666">.</span><span style="color:#4070a0">select</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;.forall-body .field-item p&#39;</span><span style="color:#666">).</span><span style="color:#4070a0">collect</span> <span style="color:#666">{</span> it<span style="color:#666">.</span><span style="color:#4070a0">text</span><span style="color:#666">()</span> <span style="color:#666">}.</span><span style="color:#4070a0">join</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;\\n\\n&#39;</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>Now I&rsquo;m gonna make a call with groovy-wslight to the NL API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@Grab</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;com.github.groovy-wslite:groovy-wslite:1.1.3&#39;</span><span style="color:#666">)</span>  
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">wslite.rest.*</span>   
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> apiKey <span style="color:#666">=</span> <span style="color:#4070a0">&#39;MY_TOP_SECRET_API_KEY&#39;</span>   
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> client <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> RESTClient<span style="color:#666">(</span><span style="color:#4070a0">&#39;https://language.googleapis.com/v1beta1/&#39;</span><span style="color:#666">)</span>  
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> result <span style="color:#666">=</span> client<span style="color:#666">.</span><span style="color:#4070a0">post</span><span style="color:#666">(</span><span style="color:#002070;font-weight:bold">path:</span> <span style="color:#4070a0">&#39;documents:annotateText&#39;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">query:</span> <span style="color:#666">[</span><span style="color:#002070;font-weight:bold">key:</span> apiKey<span style="color:#666">])</span> <span style="color:#666">{</span>  
</span></span><span style="display:flex;"><span>    type ContentType<span style="color:#666">.</span><span style="color:#4070a0">JSON</span>  
</span></span><span style="display:flex;"><span>    json <span style="color:#002070;font-weight:bold">document:</span> <span style="color:#666">[</span>  
</span></span><span style="display:flex;"><span>        type <span style="color:#666">:</span> <span style="color:#4070a0">&#39;PLAIN_TEXT&#39;</span><span style="color:#666">,</span>  
</span></span><span style="display:flex;"><span>        <span style="color:#002070;font-weight:bold">content:</span> text  
</span></span><span style="display:flex;"><span>    <span style="color:#666">],</span> <span style="color:#002070;font-weight:bold">features:</span> <span style="color:#666">[</span>  
</span></span><span style="display:flex;"><span>        extractSyntax <span style="color:#666">:</span> <span style="color:#007020;font-weight:bold">true</span><span style="color:#666">,</span>  
</span></span><span style="display:flex;"><span>        extractEntities <span style="color:#666">:</span> <span style="color:#007020;font-weight:bold">true</span><span style="color:#666">,</span>  
</span></span><span style="display:flex;"><span>        <span style="color:#002070;font-weight:bold">extractDocumentSentiment:</span> <span style="color:#007020;font-weight:bold">true</span>  
</span></span><span style="display:flex;"><span>    <span style="color:#666">]</span>  
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>  
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// returns a list of parsed sentences  
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>println result<span style="color:#666">.</span><span style="color:#4070a0">json</span><span style="color:#666">.</span><span style="color:#4070a0">sentences</span><span style="color:#666">.</span><span style="color:#4070a0">text</span><span style="color:#666">.</span><span style="color:#4070a0">content</span>  
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// prints the overall sentiment of the speech  
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>println result<span style="color:#666">.</span><span style="color:#4070a0">json</span><span style="color:#666">.</span><span style="color:#4070a0">documentSentiment</span><span style="color:#666">.</span><span style="color:#4070a0">polarity</span>
</span></span></code></pre></div><p>Groovy-wslight nicely handles XML and JSON payloads: you can use Groovy maps for the input value, which will be marshalled to JSON transparently, and the GPath notation to easily access the resulting JSON object returned by this API.</p>
<p>It was very quick and straightforward to use Jsoup and groovy-wslight for my web scraping and REST handling needs, and it was a breeze to integrate them in my App Engine application. In a follow-up article, I&rsquo;ll tell you a bit more about the sentiment analysis of the sentences of the speeches, so please stay tuned for the next installment!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Sentiment analysis on tweets</title><link>https://glaforge.dev/posts/2016/07/20/sentiment-analysis-on-tweets/</link><pubDate>Wed, 20 Jul 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/07/20/sentiment-analysis-on-tweets/</guid><description>&lt;p>What’s the mood on Twitter today? Looking at my little &lt;a href="https://glaforge.dev/posts/2016/07/11/getting-started-with-glide-and-gaelyk-on-google-app-engine/">twitter demo&lt;/a> from a few weeks ago (using &lt;a href="http://glide-gae.appspot.com/">Glide&lt;/a> &amp;amp; &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a> on &lt;a href="https://cloud.google.com/appengine/">Google App Engine&lt;/a>), I thought I could enrich the visualization with some sentiment analysis to give more color to those tweets. Fortunately, there’s a new API in Google-town, the &lt;a href="https://cloud.google.com/natural-language/docs/">Cloud Natural Language API&lt;/a> (some more info in the &lt;a href="https://cloudplatform.googleblog.com/2016/07/the-latest-for-Cloud-customers-machine-learning-and-west-coast-expansion.html">announcement&lt;/a> and a great post showing textual &lt;a href="https://cloud.google.com/blog/big-data/2016/07/using-the-cloud-natural-language-api-to-analyze-harry-potter-and-the-new-york-times">analysis of Harry Potter and New York Times&lt;/a>)!&lt;/p>
&lt;p>The brand-new Cloud Natural Language API provides three key services:&lt;/p></description><content:encoded>
<![CDATA[<p>What’s the mood on Twitter today? Looking at my little <a href="https://glaforge.dev/posts/2016/07/11/getting-started-with-glide-and-gaelyk-on-google-app-engine/">twitter demo</a> from a few weeks ago (using <a href="http://glide-gae.appspot.com/">Glide</a> &amp; <a href="http://gaelyk.appspot.com/">Gaelyk</a> on <a href="https://cloud.google.com/appengine/">Google App Engine</a>), I thought I could enrich the visualization with some sentiment analysis to give more color to those tweets. Fortunately, there’s a new API in Google-town, the <a href="https://cloud.google.com/natural-language/docs/">Cloud Natural Language API</a> (some more info in the <a href="https://cloudplatform.googleblog.com/2016/07/the-latest-for-Cloud-customers-machine-learning-and-west-coast-expansion.html">announcement</a> and a great post showing textual <a href="https://cloud.google.com/blog/big-data/2016/07/using-the-cloud-natural-language-api-to-analyze-harry-potter-and-the-new-york-times">analysis of Harry Potter and New York Times</a>)!</p>
<p>The brand-new Cloud Natural Language API provides three key services:</p>
<ul>
<li>
<p><strong>Sentiment analysis</strong>: inspects the given text and identifies the prevailing emotional opinion within the text, especially to determine a writer&rsquo;s attitude as positive, negative, or neutral.</p>
</li>
<li>
<p><strong>Entity recognition</strong>: inspects the given text for known entities (proper nouns such as public figures, landmarks, etc.) and returns information about those entities.</p>
</li>
<li>
<p><strong>Syntax analysis</strong>: extracts linguistic information, breaking up the given text into a series of sentences and tokens (generally, word boundaries), providing further analysis on those tokens.</p>
</li>
</ul>
<p>I’m going to focus only on the sentiment analysis in this article. When analyzing some text, the API tells you whether the content is negative, neutral or positive, returning “polarity” values ranging from -1 for negative to +1 for positive. And you also get a “magnitude”, from 0 to +Infinity to say how strong the emotions expressed are. You can read more about <a href="https://cloud.google.com/natural-language/docs/basics#interpreting_sentiment_analysis_values">what polarity and magnitude mean</a> for a more thorough understanding.</p>
<h2 id="lets-get-started">Let’s get started!</h2>
<p>With the code base of my <a href="https://glaforge.dev/posts/2016/07/11/getting-started-with-glide-and-gaelyk-on-google-app-engine/">first article</a>, I will add the sentiment analysis associated with the tweets I’m fetching. The idea is to come up with a colorful wall of tweets like this, with a range of colors from red for negative, to green for positive, through yellow for neutral:</p>
<p><figure>
  <a href="#img-49801babd5e439ecb5bba92b27d3508a">
    <img src="/img/misc/tweetmood-final-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-49801babd5e439ecb5bba92b27d3508a">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/tweetmood-final-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I’ll create a new controller (mood.groovy) that will call the Cloud NL service, passing the text as input. I’ll take advantage of App Engine’s Memcache support to cache the calls to the service, as tweets are immutable, their sentiment won’t change. The controller will return a JSON structure to hold the result of the sentiment analysis. From the index.gtpl view template, I’ll add a bit of JavaScript and AJAX to call my newly created controller.</p>
<h2 id="setting-up-the-dependencies">Setting up the dependencies</h2>
<p>You can either use the Cloud NL REST API or the Java SDK. I decided to use the latter, essentially just to benefit from code completion in my IDE. You can have a look at the <a href="https://cloud.google.com/natural-language/docs/samples">Java samples</a> provided. I’m updating the glide.gradle file to define my dependencies, including the google-api-services-language artifact which contains the Cloud NL service. I also needed to depend on the Google API client JARs, and Guava. Here’s what my Gradle dependencies ended up looking like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;com.google.api-client:google-api-client:1.21.0&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;com.google.api-client:google-api-client-appengine:1.21.0&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;com.google.api-client:google-api-client-servlet:1.21.0&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;com.google.guava:guava:19.0&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;com.google.apis:google-api-services-language:v1beta1-rev1-1.22.0&#34;</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;org.twitter4j:twitter4j-appengine:4.0.4&#34;</span> <span style="color:#666">}</span>
</span></span></code></pre></div><h2 id="creating-a-new-route-for-the-mood-controller">Creating a new route for the mood controller</h2>
<p>First, let’s create a new route in _routes.groovy to point at the new controller:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>post <span style="color:#4070a0">&#34;/mood&#34;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">forward:</span> <span style="color:#4070a0">&#34;/mood.groovy&#34;</span>
</span></span></code></pre></div><h2 id="coding-the-mood-controller">Coding the mood controller</h2>
<p>Now let’s code the mood.groovy controller!</p>
<p>We’ll need quite a few imports for the Google API client classes, and a couple more for the Cloud Natural Language API:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.client.googleapis.json.GoogleJsonResponseException</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.client.http.*</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.client.googleapis.auth.oauth2.GoogleCredential</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.client.googleapis.javanet.GoogleNetHttpTransport</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.client.json.jackson2.JacksonFactory</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.services.language.v1beta1.*</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">com.google.api.services.language.v1beta1.model.*</span>
</span></span></code></pre></div><p>We’re retrieving the text as a parameter, with the params map:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> text <span style="color:#666">=</span> params<span style="color:#666">.</span><span style="color:#4070a0">txt</span>
</span></span></code></pre></div><p>We’ve set up a few local variables that we’ll use for storing and returning the result of the sentiment analysis invocation:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> successOutcome <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">true</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> reason <span style="color:#666">=</span> <span style="color:#4070a0">&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> polarity <span style="color:#666">=</span> <span style="color:#40a070">0</span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> magnitude <span style="color:#666">=</span> <span style="color:#40a070">0</span>
</span></span></code></pre></div><p>Let’s check if we have already got the sentiment analysis for the text parameter in Memcache:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> cachedResult <span style="color:#666">=</span> memcache<span style="color:#666">[</span>text<span style="color:#666">]</span>
</span></span></code></pre></div><p>If it’s in the cache, we’ll be able to return it, otherwise, it’s time to compute it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(!</span>cachedResult<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">try</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#60a0b0;font-style:italic">// the sentiment analysis calling will be here
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    <span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">catch</span> <span style="color:#666">(</span>Throwable t<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        successOutcome <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">false</span>
</span></span><span style="display:flex;"><span>        reason <span style="color:#666">=</span> t<span style="color:#666">.</span><span style="color:#4070a0">message</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>We’re going to wrap our service call with a bit of exception handling, in case something goes wrong, we want to alert the user of what’s going on. And in lieu of the comment, we’ll add some logic to analyze the sentiment We must define the Google credentials allowing us to access the API. Rather than explaining the whole process, please follow the <a href="https://cloud.google.com/natural-language/docs/common/auth">authentication process</a> explained in the documentation to create an API key and a service account:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> credential <span style="color:#666">=</span> GoogleCredential<span style="color:#666">.</span><span style="color:#4070a0">applicationDefault</span><span style="color:#666">.</span><span style="color:#4070a0">createScoped</span><span style="color:#666">(</span>CloudNaturalLanguageAPIScopes<span style="color:#666">.</span><span style="color:#4070a0">all</span><span style="color:#666">())</span>
</span></span></code></pre></div><p>Now we can create our Cloud Natural Language API caller:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> api <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> CloudNaturalLanguageAPI<span style="color:#666">.</span><span style="color:#4070a0">Builder</span><span style="color:#666">(</span>
</span></span><span style="display:flex;"><span>    GoogleNetHttpTransport<span style="color:#666">.</span><span style="color:#4070a0">newTrustedTransport</span><span style="color:#666">(),</span>
</span></span><span style="display:flex;"><span>    JacksonFactory<span style="color:#666">.</span><span style="color:#4070a0">defaultInstance</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">new</span> <span style="color:#06287e">HttpRequestInitializer</span><span style="color:#666">()</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#902000">void</span> <span style="color:#06287e">initialize</span><span style="color:#666">(</span>HttpRequest httpRequest<span style="color:#666">)</span> <span style="color:#007020;font-weight:bold">throws</span> IOException <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            credential<span style="color:#666">.</span><span style="color:#4070a0">initialize</span><span style="color:#666">(</span>httpRequest<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">})</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">.</span><span style="color:#4070a0">setApplicationName</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;TweetMood&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>The caller requires some parameters like an HTTP transport, a JSON factory, and a request initializer that double checks that we’re allowed to make those API calls. Now that the API is set up, we can call it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> sentimentResponse <span style="color:#666">=</span> api<span style="color:#666">.</span><span style="color:#4070a0">documents</span><span style="color:#666">().</span><span style="color:#4070a0">analyzeSentiment</span><span style="color:#666">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">new</span> <span style="color:#06287e">AnalyzeSentimentRequest</span><span style="color:#666">(</span><span style="color:#002070;font-weight:bold">document:</span> <span style="color:#007020;font-weight:bold">new</span> Document<span style="color:#666">(</span><span style="color:#002070;font-weight:bold">content:</span> text<span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">type:</span> <span style="color:#4070a0">&#34;PLAIN\_TEXT&#34;</span><span style="color:#666">))</span>
</span></span><span style="display:flex;"><span><span style="color:#666">).</span><span style="color:#4070a0">execute</span><span style="color:#666">()</span>
</span></span></code></pre></div><p>We created an <code>AnalyzeSentimentRequest</code>, passing a <code>Document</code> to analyze with the text of our tweets. Finally, we execute that request. With the values from the response, we’re going to assign our polarity and magnitude variables:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>polarity <span style="color:#666">=</span> sentimentResponse<span style="color:#666">.</span><span style="color:#4070a0">documentSentiment</span><span style="color:#666">.</span><span style="color:#4070a0">polarity</span>
</span></span><span style="display:flex;"><span>magnitude <span style="color:#666">=</span> sentimentResponse<span style="color:#666">.</span><span style="color:#4070a0">documentSentiment</span><span style="color:#666">.</span><span style="color:#4070a0">magnitude</span>
</span></span></code></pre></div><p>Then, we’re going to store the result (successful or not) in Memcache:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>    cachedResult <span style="color:#666">=</span> <span style="color:#666">[</span>
</span></span><span style="display:flex;"><span>            <span style="color:#002070;font-weight:bold">success:</span> successOutcome<span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#002070;font-weight:bold">message:</span> reason<span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#002070;font-weight:bold">polarity:</span> sentiment<span style="color:#666">?.</span><span style="color:#4070a0">polarity</span> <span style="color:#666">?:</span> <span style="color:#40a070">0.0</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>            <span style="color:#002070;font-weight:bold">magnitude:</span> sentiment<span style="color:#666">?.</span><span style="color:#4070a0">magnitude</span> <span style="color:#666">?:</span> <span style="color:#40a070">0.0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span>    memcache<span style="color:#666">[</span>text<span style="color:#666">]</span> <span style="color:#666">=</span> cachedResult
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Now, we setup the JSON content type for the answer, and we can render the cachedResult map as a JSON object with the Groovy JSON builder available inside all controllers:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>response<span style="color:#666">.</span><span style="color:#4070a0">contentType</span> <span style="color:#666">=</span> <span style="color:#4070a0">&#39;application/json&#39;</span>
</span></span><span style="display:flex;"><span>json<span style="color:#666">.</span><span style="color:#4070a0">result</span> cachedResult
</span></span></code></pre></div><h2 id="calling-our-controller-from-the-view">Calling our controller from the view</h2>
<p>A bit of JavaScript &amp; AJAX to the rescue to call the mood controller! I wanted something a bit lighter than jQuery, so I went with <a href="http://zeptojs.com/">Zepto.js</a> for fun. It’s pretty much the same API as jQuery anyway. Just before the end of the body, you can install Zepto from a CDN with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#062873;font-weight:bold">script</span> <span style="color:#4070a0">src</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;https://cdnjs.cloudflare.com/ajax/libs/zepto/1.1.6/zepto.min.js&#34;</span>&gt;&lt;/<span style="color:#062873;font-weight:bold">script</span>&gt;
</span></span></code></pre></div><p>Then, we’ll open up our script tag for some coding:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#062873;font-weight:bold">script</span> <span style="color:#4070a0">language</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;javascript&#34;</span>&gt;
</span></span><span style="display:flex;"><span>Zepto(<span style="color:#007020;font-weight:bold">function</span>(z) {
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// some magic here!
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>});
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#062873;font-weight:bold">script</span>&gt;
</span></span></code></pre></div><p>As the sentiment analysis API call doesn’t support batch requests, we’ll have to call the API for each and every tweet. So let’s iterate over each tweet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>z(<span style="color:#4070a0">&#39;.tweet&#39;</span>).forEach(<span style="color:#007020;font-weight:bold">function</span>(e, idx) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">var</span> txt <span style="color:#666">=</span> z(e).data(<span style="color:#4070a0">&#39;text&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// ....
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>}
</span></span></code></pre></div><p>Compared to the previous article, I’ve added a data-text attribute to contain the text of the tweet, stripped from hashtags, twitter handles and links (I’ll let you use some regex magic to scratch those bits of text!).</p>
<p>Next, I call my mood controller, passing the trimmed text as input, and check if the response is successful:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span> z.post(<span style="color:#4070a0">&#39;/mood&#39;</span>, { txt<span style="color:#666">:</span> txt }, <span style="color:#007020;font-weight:bold">function</span>(resp) {
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">if</span> (resp.result.success) {
</span></span><span style="display:flex;"><span>        <span style="color:#60a0b0;font-style:italic">// …
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I retrieve the polarity and magnitude from the JSON payload returned by my mood controller:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span> polarity <span style="color:#666">=</span> resp.result.polarity;
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">var</span> magnitude <span style="color:#666">=</span> resp.result.magnitude;
</span></span></code></pre></div><p>Then I update the background color of my tweets with the following approach. I’m using the HSL color space: Hue, Saturation, Lightness.</p>
<p>The hue ranges from 0 to 360°, and for my tweets, I’m using the first third, from red / 0°, through yellow / 60°, up to green / 120° to represent the polarity, respectively with negative / -1, neutral / 0 and positive / +1.</p>
<p>The saturation (in percents) corresponds to the magnitude. For tweets which are small, the magnitude rarely goes beyond 1, so I simply multiply the magnitude by 100 to get percentages, and floors the results to 100% if it goes beyond.</p>
<p>For the lightness, I’ve got a fixed value of 80%, as 100% would always be full white!</p>
<p>Here’s a more explicit visualization of this color encoding with the following graph:</p>
<p><figure>
  <a href="#img-57d68a72b721b80fbb790923f5b842e1">
    <img src="/img/misc/mood-color-scheme-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-57d68a72b721b80fbb790923f5b842e1">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/mood-color-scheme-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>So what does the code looks like, with the DOM updates with Zepto?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">var</span> hsl <span style="color:#666">=</span> <span style="color:#4070a0">&#39;hsl(&#39;</span> <span style="color:#666">+</span>  <span style="color:#007020">Math</span>.floor((polarity <span style="color:#666">+</span> <span style="color:#40a070">1</span>) <span style="">\</span><span style="color:#666">*</span> <span style="color:#40a070">60</span>) <span style="color:#666">+</span> <span style="color:#4070a0">&#39;, &#39;</span> <span style="color:#666">+</span>  <span style="color:#007020">Math</span>.min(<span style="color:#007020">Math</span>.floor(magnitude <span style="">\</span><span style="color:#666">*</span> <span style="color:#40a070">100</span>), <span style="color:#40a070">100</span>) <span style="color:#666">+</span> <span style="color:#4070a0">&#39;%, &#39;</span> <span style="color:#666">+</span>  <span style="color:#4070a0">&#39;80%) !important&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> z(e).css(<span style="color:#4070a0">&#39;background-color&#39;</span>, hsl)
</span></span><span style="display:flex;"><span>    .data(<span style="color:#4070a0">&#39;polarity&#39;</span>, polarity)
</span></span><span style="display:flex;"><span>    .data(<span style="color:#4070a0">&#39;magnitude&#39;</span>, magnitude);
</span></span></code></pre></div><p>For the fun, I’ve also added some smileys to represent five buckets of positivity / negativity (very negative, negative, neutral, positive, very positive), and from 0 to 3 exclamation marks for 4 buckets of magnitude. That’s what you see in the bottom of the tweet cards in the final screenshot:</p>
<p><figure>
  <a href="#img-12ab00628c6e8259110082da8fe099bf">
    <img src="/img/misc/example-colored-tweet-small.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-12ab00628c6e8259110082da8fe099bf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/example-colored-tweet-small.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<h2 id="summary">Summary</h2>
<p>But we’re actually done! We have our controller fetching the tweets forwarding to the view template from the last article, and we added a bit of JavaScript &amp; AJAX to call our new mood controller, to display some fancy colors to represent the mood of our tweets, using the brand new <a href="https://cloud.google.com/natural-language/docs/">Cloud Natural Language API</a>.</p>
<p>When playing with sentiment analysis, I was generally on the same opinion regarding sentiment of the tweets, but I was sometimes surprised by the outcome. It’s hard for short bursts of text like tweets to decipher things like irony, sarcasm, etc, and a particular tweet might appear positive when reality it isn’t, and vice versa. Sentiment analysis is probably not an exact science, and you need more context to decide what’s really positive or negative.</p>
<p>Without even speaking of sarcasm or irony, sometimes certain tweets were deemed negative when some particular usually negative words appeared: a “no” or “not” is not necessarily negative when it’s negating something already negative, turning it into something more positive (“it’s not uncool”). For longer text, the general sentiment seems more accurate, so perhaps it’s more appropriate to use sentiment analysis in such cases than on short snippets.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Getting started with Glide and Gaelyk on Google App Engine</title><link>https://glaforge.dev/posts/2016/07/11/getting-started-with-glide-and-gaelyk-on-google-app-engine/</link><pubDate>Mon, 11 Jul 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/07/11/getting-started-with-glide-and-gaelyk-on-google-app-engine/</guid><description>&lt;p>Back in 2009, I created &lt;a href="http://gaelyk.appspot.com/">Gaelyk&lt;/a>, a lightweight toolkit for developing &lt;a href="https://cloud.google.com/appengine">Google App Engine&lt;/a>apps using the &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a>programming language. I even had the chance to &lt;a href="https://www.youtube.com/watch?v%3DNEnniZTdOYk">speak at Google I/O 2009&lt;/a>about it! Good times, good times… &lt;a href="https://twitter.com/musketyr">Vladimír Oraný&lt;/a>later joined me in maintaining and evolving Gaelyk, and &lt;a href="https://twitter.com/kdabir">Kunal Dabir&lt;/a>created the fun &lt;a href="http://glide-gae.appspot.com/">Glide&lt;/a> project, which is a thin wrapper around Gaelyk to further streamline the development of small to mid-sized apps for Google App Engine.&lt;/p>
&lt;p>Today, I want to share with you a quick start guide to develop a little app, that shows some tweets from selected accounts with the &lt;a href="https://dev.twitter.com/rest/public">Twitter API&lt;/a>(thanks to &lt;a href="http://twitter4j.org/en/">Twitter4J&lt;/a>), and using the &lt;a href="https://getmdl.io/">Material Design Light&lt;/a> template for the look’n feel (I used the “&lt;a href="https://getmdl.io/templates/dashboard/index.html">dashboard&lt;/a>” template). I won’t list all the exact steps, all the precise changes made to the templates, etc, but I want to give you the keys for having a productive experience with Glide and Gaelyk on App Engine. And here’s a screenshot of what we’ll be building:&lt;/p></description><content:encoded>
<![CDATA[<p>Back in 2009, I created <a href="http://gaelyk.appspot.com/">Gaelyk</a>, a lightweight toolkit for developing <a href="https://cloud.google.com/appengine">Google App Engine</a>apps using the <a href="http://groovy-lang.org/">Apache Groovy</a>programming language. I even had the chance to <a href="https://www.youtube.com/watch?v%3DNEnniZTdOYk">speak at Google I/O 2009</a>about it! Good times, good times… <a href="https://twitter.com/musketyr">Vladimír Oraný</a>later joined me in maintaining and evolving Gaelyk, and <a href="https://twitter.com/kdabir">Kunal Dabir</a>created the fun <a href="http://glide-gae.appspot.com/">Glide</a> project, which is a thin wrapper around Gaelyk to further streamline the development of small to mid-sized apps for Google App Engine.</p>
<p>Today, I want to share with you a quick start guide to develop a little app, that shows some tweets from selected accounts with the <a href="https://dev.twitter.com/rest/public">Twitter API</a>(thanks to <a href="http://twitter4j.org/en/">Twitter4J</a>), and using the <a href="https://getmdl.io/">Material Design Light</a> template for the look’n feel (I used the “<a href="https://getmdl.io/templates/dashboard/index.html">dashboard</a>” template). I won’t list all the exact steps, all the precise changes made to the templates, etc, but I want to give you the keys for having a productive experience with Glide and Gaelyk on App Engine. And here’s a screenshot of what we’ll be building:</p>
<p><figure>
  <a href="#img-8276ef0a9d985887a00406341f4f3c0e">
    <img src="/img/misc/tweet-glide.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-8276ef0a9d985887a00406341f4f3c0e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/tweet-glide.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Ready? Let’s start!</p>
<h2 id="installing-glide">Installing Glide</h2>
<p>In the Groovy community, most developers these days are using <a href="http://sdkman.io/">SDKMan</a> to install SDKs for Groovy, Gradle, Grails, and more. Glide also comes in the form of an SDK, with a command-line, and is available via SDKMan. So, first step, let’s install SDKMan from your shell (there’s also a Windows-friendly version):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ curl -s <span style="color:#4070a0">&#34;https://get.sdkman.io&#34;</span> | bash
</span></span></code></pre></div><p>It will automatically install the SDK manager. Then, either you just open up a new terminal, or you run the following command, to have access to SDKMan in your current session:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ <span style="color:#007020">source</span> <span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$HOME</span><span style="color:#4070a0">/.sdkman/bin/sdkman-init.sh&#34;</span>
</span></span></code></pre></div><p>To check the installation succeeded, you can run it with the sdk command, for example by printing the current version of SDKMan:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sdk version
</span></span></code></pre></div><p>Now that SDKMan is installed, it’s time to install Glide as well:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sdk install glide
</span></span></code></pre></div><p>You can then check that glide is indeed functioning correctly by executing:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ glide
</span></span></code></pre></div><p>If you’re on Windows or if you’re not planning to keep SDKMan around, you can also <a href="http://glide-gae.appspot.com/docs/installing">install Glide by other means</a>, manually, as explained in the documentation.</p>
<h2 id="creating-the-skeleton-of-our-application">Creating the skeleton of our application</h2>
<p>Okay, we’re ready to create our first Glide / Gaelyk application! (You can also check out the Glide <a href="http://glide-gae.appspot.com/docs/quick-start">tutorial</a> as well)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ glide --app tweetapp create 
</span></span><span style="display:flex;"><span>$ <span style="color:#007020">cd</span> tweetapp 
</span></span><span style="display:flex;"><span>$ glide run
</span></span></code></pre></div><p>Head over to your browser at http://localhost:8080/, and you’ll see a brilliant “hello glide” message showing up. So far so good, the app is running locally, transparently thanks to the App Engine SDK, now let’s tweak this skeleton!</p>
<p>The project structure is pretty simple, in the directory, you’ll see a glide.groovy file at the root, and an “app” sub-folder containing index.groovy and <code>_routes.groovy</code>:</p>
<ul>
<li><code>glide.groovy</code> — the configuration file for your app</li>
<li><code>index.groovy</code> — the default controller</li>
<li><code>_routes.groovy</code> — listing the mappings between URLs and controllers, and more</li>
</ul>
<h2 id="configuring-our-application">Configuring our application</h2>
<p>In <code>glide.groovy</code>, you’ll have the app name and version name defined:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>app <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>    name<span style="color:#666">=</span><span style="color:#4070a0">&#34;my-tweet-demo&#34;</span> 
</span></span><span style="display:flex;"><span>    version<span style="color:#666">=</span><span style="color:#4070a0">&#34;1&#34;</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>You might have to change the application name, as we shall see later on, when we deploy the application.</p>
<p>To use the latest version of the App Engine SDK, you can append the following to explicitly ask for a specific version of the SDK:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>glide <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>    versions <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>        appengineVersion <span style="color:#666">=</span> <span style="color:#4070a0">&#34;1.9.38&#34;</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><h2 id="defining-library-dependencies">Defining library dependencies</h2>
<p>At the root of our project, we’ll actually add a new configuration file: glide.gradle. This file will allow us to define library dependencies. It’s basically a fragment of a <a href="https://gradle.org/">Gradle</a>build configuration, where you can define those dependencies using the usual Gradle <a href="https://docs.gradle.org/current/userguide/artifact_dependencies_tutorial.html">syntax</a>. In our glide.gradle file, we’ll add the following dependency, for our Twitter integration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>dependencies <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#34;org.twitter4j:twitter4j-appengine:4.0.4&#34;</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><h2 id="using-the-material-design-lite-template">Using the Material Design Lite template</h2>
<p>To make things pretty, we’ll be using the <a href="https://getmdl.io/templates/index.html">Material Design Lite dashboard sample</a>, but feel free to skip this part if you want to go straight to the coding part! Download the ZIP archive. It comes with an index.html file, as well as a style.css stylesheet. We’ll copy both files to the app/ folder, but we’ll rename index.html into index.gtpl (to make it a Groovy template file).</p>
<p>When you have a bigger project, with more assets, it’s obviously better to organize these views, stylesheets, JavaScript files, images, etc, in their own respective sub-folders. But for the purpose of my demo, I’ll keep everything in the same place.</p>
<p>You’ll see the template installed and visible if you go to this local URL:</p>
<p><a href="http://localhost:8080/index.gtpl">http://localhost:8080/index.gtpl</a></p>
<p>I won’t detail all the changes to make to the template, and I’ll let you clean the template yourselves, but we can already remove everything that’s inside the inner div of the main tag: that’s where we’ll display our tweets!</p>
<h2 id="lets-make-pretty-urls">Let&rsquo;s make pretty URLs!</h2>
<p>We’d like to have some nice URLs for our app. For that, we’ll now have a look at the _routes.groovy file where you can define your URL mappings, to point at templates (*.gtpl files) or at controllers (*.groovy files, that can render some output directly or forward to templates for rich views). What shall we put in our routes definitions?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>get <span style="color:#4070a0">&#34;/&#34;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">redirect:</span> <span style="color:#4070a0">&#34;/u/glaforge&#34;</span>  
</span></span><span style="display:flex;"><span>get <span style="color:#4070a0">&#34;/u/@who&#34;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">forward:</span> <span style="color:#4070a0">&#34;/index.groovy?u=@who&#34;</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">validate:</span> <span style="color:#666">{</span> request<span style="color:#666">.</span><span style="color:#4070a0">who</span> <span style="color:#666">==~</span> <span style="color:#4070a0">/[a-zA-Z0-9_]{1,15}/</span> <span style="color:#666">},</span>  
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">cache:</span> <span style="color:#40a070">1</span><span style="color:#666">.</span><span style="color:#4070a0">minute</span>  
</span></span><span style="display:flex;"><span>get <span style="color:#4070a0">&#34;/u/@who&#34;</span><span style="color:#666">,</span> <span style="color:#002070;font-weight:bold">forward:</span> <span style="color:#4070a0">&#34;/index.groovy?u=@who&amp;error=invalid&#34;</span>
</span></span></code></pre></div><p>You can have a look at the Gaelyk documentation that defines the <a href="http://gaelyk.appspot.com/tutorial/url-routing%23route-definition">routes definition syntax</a> for further explanations on what’s possible.</p>
<p>The root of the app, <code>/</code>, will redirect to /u/glaforge, to visualize my latest tweets. And all URLs like <code>/u/*</code> will forward to our index.groovy controller, that will fetch the tweets for that Twitter user, and forward them to the index.gtpl view for rendering the result.</p>
<pre tabindex="0"><code>/u/glaforge → /index.groovy?u=glaforge → /index.gtpl
</code></pre><p>The routing syntax is using the <code>@foo</code> notation to denote query path variables, that we can then reuse in the forwarding part.</p>
<p>The routing rules are evaluated in order, and the first one that matches the URL will be chosen. We have two get <code>/u/@who</code> routes, in the first case, we have a validation rule that checks that the @who path variable is a valid Twitter handle (using Groovy’s <a href="http://docs.groovy-lang.org/latest/html/documentation/index.html%23_regular_expression_operators">regular expression matching</a> operator). If the validation fails, this route isn’t chosen, and the chain will continue, and it will fall back to the following route that forwards to the template with an error query parameter.</p>
<p>Also interesting to note is the use of caching, with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#002070;font-weight:bold">cache:</span> <span style="color:#40a070">1</span><span style="color:#666">.</span><span style="color:#4070a0">minute</span>
</span></span></code></pre></div><p>The output of this URL will be put in App Engine’s Memcache so that for the next minute, all requests to the same URL will be fetched from the cache, rather than having to call again the controller and the Twitter API, thus saving on computation and on third-party API call quota.</p>
<p>For the purpose of development, you might want to comment that caching configuration, as you do want to see changes to that template or controller as you’re making changes.</p>
<h2 id="time-to-code-our-tweet-fetching-controller">Time to code our tweet fetching controller</h2>
<p>To user the Twitter API, you’ll have to register a new application on the <a href="https://apps.twitter.com/">Twitter Apps page</a>. Twitter will give you the right credentials that you’ll need to connect to the API. You’ll need the four following keys to configure the Twitter4J library:</p>
<ul>
<li>the consumer API key</li>
<li>the secrete consumer API key</li>
<li>the access token</li>
<li>and the secret access token</li>
</ul>
<p>Let’s configure Twitter4J with that information. I’ll implement the “happy path” and will skip part of the proper error handling (an exercise for the reader?), to keep the code short for this article.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">twitter4j.*</span> 
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">twitter4j.conf.*</span>   
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> conf <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> ConfigurationBuilder<span style="color:#666">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">debugEnabled:</span> <span style="color:#007020;font-weight:bold">true</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">OAuthAccessToken:</span> <span style="color:#4070a0">&#34;CHANGE_ME&#34;</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">OAuthAccessTokenSecret:</span> <span style="color:#4070a0">&#34;CHANGE_ME&#34;</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">OAuthConsumerKey:</span> <span style="color:#4070a0">&#34;CHANGE_ME&#34;</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">OAuthConsumerSecret:</span> <span style="color:#4070a0">&#34;CHANGE_ME&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">.</span><span style="color:#4070a0">build</span><span style="color:#666">()</span>   
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">def</span> twitter <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> TwitterFactory<span style="color:#666">(</span>conf<span style="color:#666">).</span><span style="color:#4070a0">instance</span>
</span></span></code></pre></div><p>The API is configured with your credentials. Be sure to replace all the <code>CHANGE_ME</code> bits, obviously!</p>
<p>Let’s lookup the Twitter handle coming through the query parameter, thanks to the user ‘u’ attribute on the params map:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#902000">def</span> accounts <span style="color:#666">=</span> twitter<span style="color:#666">.</span><span style="color:#4070a0">lookupUsers</span><span style="color:#666">(</span>params<span style="color:#666">.</span><span style="color:#4070a0">u</span><span style="color:#666">)</span>
</span></span></code></pre></div><p>There should only be two cases (that’s where there may be some more error handling to do!): 1) there’s no user found, or there’s only one. Let’s start with no user found:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>accounts<span style="color:#666">.</span><span style="color:#4070a0">isEmpty</span><span style="color:#666">())</span> <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>    request<span style="color:#666">.</span><span style="color:#4070a0">errorMessage</span> <span style="color:#666">=</span> <span style="color:#4070a0">&#34;Account &#39;${params.u}&#39; doesn&#39;t exist.&#34;</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>If no user account was found, we’ll put an error message in the request that’ll be forwarded to our view template.</p>
<p>In the else branch, we’ll handle the the normal case where the user was found:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">else</span> <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>    User userAccount <span style="color:#666">=</span> accounts<span style="color:#666">[</span><span style="color:#40a070">0</span><span style="color:#666">]</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#902000">def</span> tweets <span style="color:#666">=</span> twitter<span style="color:#666">.</span><span style="color:#4070a0">search</span><span style="color:#666">(</span><span style="color:#007020;font-weight:bold">new</span> Query<span style="color:#666">(</span><span style="color:#4070a0">&#34;from:${params.u}&#34;</span><span style="color:#666">))</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">.</span><span style="color:#4070a0">tweets</span><span style="color:#666">.</span><span style="color:#4070a0">findAll</span> <span style="color:#666">{</span> <span style="color:#666">!</span>it<span style="color:#666">.</span><span style="color:#4070a0">isRetweet</span><span style="color:#666">()</span> <span style="color:#666">}</span>
</span></span></code></pre></div><p>We get the first account returned, and issue a search request for the latest tweets from that account. We filter out the retweets to keep only the user’s original tweets (but it’s up to you if you want to keep them).</p>
<p>In the request for the view, we’ll add details about the account:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>request<span style="color:#666">.</span><span style="color:#4070a0">account</span> <span style="color:#666">=</span> <span style="color:#666">[</span> 
</span></span><span style="display:flex;"><span>    name <span style="color:#666">:</span> userAccount<span style="color:#666">.</span><span style="color:#4070a0">name</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">handle:</span> userAccount<span style="color:#666">.</span><span style="color:#4070a0">screenName</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#002070;font-weight:bold">avatar:</span> userAccount<span style="color:#666">.</span><span style="color:#4070a0">biggerProfileImageURL</span>  
</span></span><span style="display:flex;"><span><span style="color:#666">]</span>
</span></span></code></pre></div><p>And we’ll also add the list of tweets:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>request<span style="color:#666">.</span><span style="color:#4070a0">tweets</span> <span style="color:#666">=</span> tweets<span style="color:#666">.</span><span style="color:#4070a0">collect</span> <span style="color:#666">{</span> Status s <span style="color:#666">-&gt;</span> <span style="color:#666">[</span>  
</span></span><span style="display:flex;"><span>        id <span style="color:#666">:</span> s<span style="color:#666">.</span><span style="color:#4070a0">id</span><span style="color:#666">,</span> 
</span></span><span style="display:flex;"><span>        <span style="color:#002070;font-weight:bold">timestamp:</span> s<span style="color:#666">.</span><span style="color:#4070a0">createdAt</span><span style="color:#666">.</span><span style="color:#4070a0">time</span><span style="color:#666">,</span>  
</span></span><span style="display:flex;"><span>        content <span style="color:#666">:</span> s<span style="color:#666">.</span><span style="color:#4070a0">text</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">]</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>And to finish our controller, we’ll forward to the view:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>forward <span style="color:#4070a0">&#39;index.gtpl&#39;</span>
</span></span></code></pre></div><p>Now that our controller is ready, we’ll have to surface the data into the view template.</p>
<h2 id="modify-the-view-template">Modify the view template</h2>
<p>Wherever the template displays the “Home” label, we’ll replace these with the Twitter handle. For that, we can use String interpolation in the template with the ${} notation. If there’s no error message, there should be an account, and we display that handle.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>$<span style="color:#666">{</span> request<span style="color:#666">.</span><span style="color:#4070a0">errorMessage</span> <span style="color:#666">?</span> <span style="color:#4070a0">&#39;Home&#39;</span> <span style="color:#666">:</span> <span style="color:#4070a0">&#39;@&#39;</span> <span style="color:#666">+</span>request<span style="color:#666">.</span><span style="color:#4070a0">account</span><span style="color:#666">.</span><span style="color:#4070a0">handle</span> <span style="color:#666">}</span>
</span></span></code></pre></div><p>Let’s display the list of tweets, or the error message if there’s one. We’ll iterate over the tweets from the request attributes, and add the following in the inner div of the main tag (fore brevity sake, I&rsquo;ll remove the divs and css needed to make things pretty):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#666">&lt;%</span> 
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>request<span style="color:#666">.</span><span style="color:#4070a0">tweets</span><span style="color:#666">)</span> <span style="color:#666">{</span> 
</span></span><span style="display:flex;"><span>    request<span style="color:#666">.</span><span style="color:#4070a0">tweets</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> tweet <span style="color:#666">-&gt;</span> <span style="color:#666">%&gt;</span> $<span style="color:#666">{</span>tweet<span style="color:#666">.</span><span style="color:#4070a0">content</span><span style="color:#666">}</span> <span style="color:#666">&lt;%</span> <span style="color:#666">}</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">else</span> <span style="color:#666">{</span> <span style="color:#666">%&gt;</span> 
</span></span><span style="display:flex;"><span>    $<span style="color:#666">{</span>request<span style="color:#666">.</span><span style="color:#4070a0">errorMessage</span><span style="color:#666">}</span>  
</span></span><span style="display:flex;"><span><span style="color:#666">&lt;%</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span> <span style="color:#666">%&gt;</span>
</span></span></code></pre></div><p>And voila, our app is ready! Well, at least, it works locally on our app server, but it’s time to deploy it for real on App Engine!</p>
<h2 id="deploying-to-google-app-engine">Deploying to Google App Engine</h2>
<p>Let’s login in the <a href="https://console.cloud.google.com">Google Cloud Platform console</a>to create our application project. If you don’t already have an account, you can benefit from the <a href="https://cloud.google.com/free-trial/">free trial which offers $300 of credits</a> for the full platform.</p>
<p><figure>
  <a href="#img-aabce51e7d3d6277c5b3f76237b48433">
    <img src="/img/src/app-engine-project-creation.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-aabce51e7d3d6277c5b3f76237b48433">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/src/app-engine-project-creation.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Be sure to pay attention to the actual project ID that will have been created, it may be slightly different than the project name itself. This project ID is also called the app ID, and that’s the actually what you have to put in the glide.groovy file, in the app.name field (right, it’s a bit confusing, isn’t it?)</p>
<p>When the project is created, you’re able to use the glide command-line to deploy the application:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ glide upload
</span></span></code></pre></div><p>If you see an error like below in the logs, it might mean that there’s a problem with your app ID, so be sure to double check it’s correct:</p>
<blockquote>
<p>403 Forbidden You do not have permission to modify this app (app_id=u&rsquo;s~my-tweet-demo&rsquo;).</p></blockquote>
<p>Another occurrence of this error message is when you are using different accounts with Google Cloud Platform. For instance, in my case, I have both a personal gmail account for my personal apps, and a google.com account for my work related apps. I had to zap <code>~/.appcfg_oauth2_tokens_java</code> to let the upload logic to use the correct account, and ask me to authentication with OAuth2.</p>
<p>Once the upload succeeded, you can access your app here:</p>
<p><a href="http://my-tweet-demo.appspot.com">http://my-tweet-demo.appspot.com</a></p>
<p>Hooray, you’ve done it! :-)</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>What can we learn from million lines of Groovy code on Github?</title><link>https://glaforge.dev/posts/2016/07/06/what-can-we-learn-from-million-lines-of-groovy-code-on-github/</link><pubDate>Wed, 06 Jul 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/07/06/what-can-we-learn-from-million-lines-of-groovy-code-on-github/</guid><description>&lt;p>Github and Google recently announced and released the Github archive to BigQuery, liberating a huge dataset of source code in multiple programming languages, and making it easier to query it and discover some insights.&lt;/p>
&lt;p>Github explained that &lt;a href="https://github.com/blog/2201-making-open-source-data-more-available">the dataset comprises over 3 terabytes&lt;/a> of data, for 2.8 million repositories, 145 million commits over 2 billion file paths! The &lt;a href="https://cloudplatform.googleblog.com">Google Cloud Platform blog&lt;/a> gave some &lt;a href="https://cloudplatform.googleblog.com/2016/06/GitHub-on-BigQuery-analyze-all-the-open-source-code.html">additional pointers&lt;/a> to give hints about what’s possible to do with the querying capabilities of BigQuery. Also, you can have a look at the &lt;a href="https://cloud.google.com/bigquery/public-data/github">getting started guide&lt;/a> with the steps to follow to have fun yourself with the dataset.&lt;/p></description><content:encoded>
<![CDATA[<p>Github and Google recently announced and released the Github archive to BigQuery, liberating a huge dataset of source code in multiple programming languages, and making it easier to query it and discover some insights.</p>
<p>Github explained that <a href="https://github.com/blog/2201-making-open-source-data-more-available">the dataset comprises over 3 terabytes</a> of data, for 2.8 million repositories, 145 million commits over 2 billion file paths! The <a href="https://cloudplatform.googleblog.com">Google Cloud Platform blog</a> gave some <a href="https://cloudplatform.googleblog.com/2016/06/GitHub-on-BigQuery-analyze-all-the-open-source-code.html">additional pointers</a> to give hints about what’s possible to do with the querying capabilities of BigQuery. Also, you can have a look at the <a href="https://cloud.google.com/bigquery/public-data/github">getting started guide</a> with the steps to follow to have fun yourself with the dataset.</p>
<p>My colleagues Felipe gave some <a href="https://medium.com/@hoffa/github-on-bigquery-analyze-all-the-code-b3576fd2b150#.hxdz073uu">interesting stats</a> about the top programming languages, or licenses, while Francesc did some interesting <a href="https://medium.com/google-cloud/analyzing-go-code-with-bigquery-485c70c3b451#.7qw4hmvly">analysis of Go repositories</a>. So I was curious to investigate myself this dataset to run some queries about the <a href="http://groovy-lang.org/">Groovy programming language</a>!</p>
<p>Without further ado, let’s dive in!</p>
<p>If you don’t already have an account of the Google Cloud Platform, you’ll be able to get the <a href="https://cloud.google.com/free-trial">free trial</a>, with $300 of credits to discover and have fun with all the products and services of the platform. Then, be sure to have a look at the <a href="https://cloud.google.com/bigquery/public-data/github#github">Github dataset getting started guide</a> I’ve mentioned above which can give you some ideas of things to try out, and the relevant steps to start tinkering with the data.</p>
<p>In the Google Cloud Platform <a href="https://console.cloud.google.com/">console</a>, I’ve created an empty project (for me, called “github-groovy-files”) that will host my project and the subset of the whole dataset to focus on the Groovy source files only.</p>
<p>Next, we can go to the Github public dataset on BigQuery:<br />
<a href="https://bigquery.cloud.google.com/dataset/bigquery-public-data:github_repos">https://bigquery.cloud.google.com/dataset/bigquery-public-data:github_repos</a></p>
<p>I created a new dataset called “github”, whose location is in the US (the default). Be sure to keep the default location in the US as the Github dataset is in that region already.</p>
<p><figure>
  <a href="#img-d93e390b9401f359b9b6d5621e57d79e">
    <img src="/img/bq-groovy/create-new-dataset-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d93e390b9401f359b9b6d5621e57d79e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/create-new-dataset-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p><figure>
  <a href="#img-08da482b415b1707724cdfa9e1b4e0bb">
    <img src="/img/bq-groovy/create-new-dataset-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-08da482b415b1707724cdfa9e1b4e0bb">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/create-new-dataset-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I launched the following query to list all the Groovy source files, and save them in a new table called “files” for further querying:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.files]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">RIGHT</span>(path,<span style="color:#bbb"> </span><span style="color:#40a070">7</span>)<span style="color:#bbb"> </span><span style="color:#666">=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;.groovy&#39;</span><span style="color:#bbb"> 
</span></span></span></code></pre></div><p>Now that I have my own subset of the dataset with only the Groovy files, I ran a count query to know the number of Groovy files available:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.files]<span style="color:#bbb"> 
</span></span></span></code></pre></div><p>There are 743 070 of Groovy source files!</p>
<p>I was curious to see if there were some common names of Groovy scripts and classes that would appear more often than others:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>TOP(filename,<span style="color:#bbb"> </span><span style="color:#40a070">24</span>),<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="">\</span><span style="color:#666">*</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>n<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LAST</span>(SPLIT(path,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;/&#39;</span>))<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">as</span><span style="color:#bbb"> </span>filename<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github.files]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span></code></pre></div><p><figure>
  <a href="#img-e15bcb10e94a0132a06b9ede95fd55ce">
    <img src="/img/bq-groovy/frequent-name-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-e15bcb10e94a0132a06b9ede95fd55ce">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/frequent-name-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
 <figure>
  <a href="#img-d3371590fc47e4a91fd7fd192f994508">
    <img src="/img/bq-groovy/frequent-name-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d3371590fc47e4a91fd7fd192f994508">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/frequent-name-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I was surprised to see A.groovy being the most frequent file name! I haven’t dived deeper yet, but I’d be curious to see what’s in those A.groovy files, as well as B.groovy or a.groovy in 4th and 13th positions respectively.</p>
<p><a href="http://groovy-lang.org/">Apache Groovy</a> is often used for various automation tasks, and I’ve found many Maven or Jenkins scripts to check that a certain task or job terminated correctly thanks to scripts called verify.groovy.</p>
<p>Files like <code>BuildConfig.groovy</code>, <code>Config.groovy</code>, <code>UrlMappings.groovy</code>, <code>DataSource.groovy</code>, <code>BootStrap.groovy</code> clearly come from the usual files found in <a href="https://grails.org/">Grails framework</a> web applications.</p>
<p>You can also see configuration files like <code>logback.groovy</code> to configure the Logback logging library.</p>
<p>You don’t see usage of the <a href="https://gradle.org/">Gradle</a> build automation tool here, because I only selected files with a .groovy extension, and not files with the .gradle extension. But we’ll come back to Gradle in a moment.</p>
<p>So far, we’ve looked at the file names only, not at their content. That’s where we need another table, coming from the “contents” table of the dataset, that we’ll filter thanks to the file names we’ve saved in our “files” table, thanks to this query:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#666">*</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[bigquery<span style="color:#666">-</span><span style="color:#007020;font-weight:bold">public</span><span style="color:#666">-</span><span style="color:#007020;font-weight:bold">data</span>:github_repos.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>id<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">IN</span><span style="color:#bbb"> </span>(<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github.files]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb">
</span></span></span></code></pre></div><p>As this is a lot of content, I had to save the result of the query in a new table called “contents”, and I had to check the box “allow large results” in the options pane that you can open thanks to the “Show options” button below the query editor.</p>
<p>From the 743 070 files, how many lines of Groovy code do you think there are in them? For that purpose, we need to split the raw content of the files per lines, as follows:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(line)<span style="color:#bbb"> </span>total_lines<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span>line<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span></code></pre></div><p>We have 16,464,376 lines of code over the our 743,070 Groovy files. That’s an average of 22 lines per file, which is pretty low! It would be more interesting to draw some histogram to see the distribution of those lines of code. We can use <a href="https://en.wikipedia.org/wiki/Quantile">quantiles</a> to have a better idea of the distribution with this query with 10 quantiles:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>QUANTILES(total_lines,<span style="color:#bbb"> </span><span style="color:#40a070">10</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span>q<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(line)<span style="color:#bbb"> </span>total_lines<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span>line,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>id<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span></code></pre></div><p>Which gives this resulting table:</p>
<p><figure>
  <a href="#img-bee31c82325b83716df6403d5372e7ba">
    <img src="/img/bq-groovy/quantiles-lines.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-bee31c82325b83716df6403d5372e7ba">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/quantiles-lines.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>There are files with 0 lines of code! And the biggest one is 9506 lines long! 10% are 11 lines long or less, half are 37 lines or less, etc. And 10% are longer than 149 lines.</p>
<p>Let’s now have a look at packages and imports for a change.</p>
<p>Do you know what are the most frequent packages used?</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>package,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39; (\[a-z0-9\\.\_\]\*)\\.&#39;</span>)<span style="color:#bbb"> </span>package,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span>line,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>content<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">CONTAINS</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;import&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">HAVING</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LEFT</span>(line,<span style="color:#bbb"> </span><span style="color:#40a070">6</span>)<span style="color:#666">=</span><span style="color:#4070a0">&#39;import&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>package,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#40a070">1</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">DESC</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">LIMIT</span><span style="color:#bbb"> </span><span style="color:#40a070">30</span>;<span style="color:#bbb">
</span></span></span></code></pre></div><p><figure>
  <a href="#img-a443e40e4ab25b8ed869b9f6270388a6">
    <img src="/img/bq-groovy/package-name-1.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a443e40e4ab25b8ed869b9f6270388a6">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/package-name-1.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
 <figure>
  <a href="#img-b400568385d30ecf627edb82b00c0c07">
    <img src="/img/bq-groovy/package-name-2.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-b400568385d30ecf627edb82b00c0c07">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/package-name-2.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
 <figure>
  <a href="#img-3ed131ef97b21af0fa9a854cd67144f4">
    <img src="/img/bq-groovy/package-name-3.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-3ed131ef97b21af0fa9a854cd67144f4">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/package-name-3.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The <a href="http://docs.spockframework.org/">Spock</a> and JUnit testing frameworks are the most widely used packages, showing that Groovy is used a lot for testing! We also see a lot of <a href="https://grails.org/">Grails</a> and <a href="https://gradle.org/">Gradle</a> related packages, and some logging, some Spring, Joda-Time, Java util-concurrent or servlets, etc.</p>
<p>We can zoom in the <code>groovy.*</code> packages with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>package,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39; ([a-z0-9\._]\*)\.&#39;</span>)<span style="color:#bbb"> </span>package,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span>line,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>content<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">CONTAINS</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;import&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">HAVING</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LEFT</span>(line,<span style="color:#bbb"> </span><span style="color:#40a070">6</span>)<span style="color:#666">=</span><span style="color:#4070a0">&#39;import&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>package,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>package<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;groovy.%&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#40a070">1</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">DESC</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">LIMIT</span><span style="color:#bbb"> </span><span style="color:#40a070">10</span>;<span style="color:#bbb"> 
</span></span></span></code></pre></div><p>And <code>groovy.transform</code> is unsurprisingly the winner, as it’s where all Groovy AST transformations reside, providing useful code generation capabilities saving developers from writing tedious repetitive code for common tasks (<code>@Immutable</code>, <code>@Delegate</code>, etc.) After transforms come <code>groovy.util.logging</code> for logging, <code>groovy.json</code> for working with JSON files, <code>groovy.sql</code> for interacting with databases through JDBC, <code>groovy.xml</code> to parse and produce XML payloads, and <code>groovy.text</code> for templating engines:</p>
<p><figure>
  <a href="#img-18fa7ae12a79c5553bb7156790aab30e">
    <img src="/img/bq-groovy/groovy-packages.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-18fa7ae12a79c5553bb7156790aab30e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/groovy-packages.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>With Groovy AST transformations being so prominent, we can also look at the most frequently used AST transformations with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>TOP(class_name,<span style="color:#bbb"> </span><span style="color:#40a070">10</span>)<span style="color:#bbb"> </span>class_name,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">COUNT</span>(<span style="color:#666">*</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP_EXTRACT(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39; [a-z0-9\._]*\.([a-zA-Z0-9_]*)&#39;</span>)<span style="color:#bbb"> </span>class_name,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span>line,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>content<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">CONTAINS</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;import&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>line<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">LIKE</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;%groovy.transform.%&#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>class_name,<span style="color:#bbb"> </span>id<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>class_name<span style="color:#bbb"> </span><span style="color:#666">!=</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;null&#39;</span><span style="color:#bbb"> 
</span></span></span></code></pre></div><p>And we get:</p>
<p><figure>
  <a href="#img-869231108d31b0ca74b1254069f1a506">
    <img src="/img/bq-groovy/frequent-ast-xforms.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-869231108d31b0ca74b1254069f1a506">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/bq-groovy/frequent-ast-xforms.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>The <code>@CompileStatic</code> transformation is the king! Followed by <code>@ToString</code> and <code>@EqualsAndHashCode</code>. But then <code>@TypeChecked</code> is fourth, showing that the static typing and compilation support of Groovy is really heavily used. Other interesting transforms used follow with <code>@Canonical</code>, <code>@PackageScope</code>, <code>@InheritConstructors</code>, <code>@Immutable</code> or <code>@TupleConstructor</code>.</p>
<p>As I was exploring imports, I also wondered whether aliased imports was often seen or not:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>aliased,<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">count</span>(aliased)<span style="color:#bbb"> </span>total<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>REGEXP<span style="">\</span>_MATCH(line,<span style="color:#bbb"> </span>r<span style="color:#4070a0">&#39;.* (as) .*&#39;</span>)<span style="color:#bbb"> </span>aliased<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>(<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>SPLIT(content,<span style="color:#bbb"> </span><span style="color:#4070a0">&#39;\n&#39;</span>)<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">AS</span><span style="color:#bbb"> </span>line<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">        </span><span style="color:#007020;font-weight:bold">FROM</span><span style="color:#bbb"> </span>[github<span style="color:#666">-</span>groovy<span style="color:#666">-</span>files:github.contents]<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span><span style="color:#007020;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>line<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">CONTAINS</span><span style="color:#bbb"> </span><span style="color:#4070a0">&#39;import &#39;</span><span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>)<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">BY</span><span style="color:#bbb"> </span>aliased<span style="color:#bbb"> 
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span><span style="color:#007020;font-weight:bold">LIMIT</span><span style="color:#bbb"> </span><span style="color:#40a070">100</span><span style="color:#bbb"> 
</span></span></span></code></pre></div><p>Interestingly, there are 2719 aliased imports over 765281 non aliased ones, that’s about 0.36%, so roughly 1 <code>import … as …</code> for 300 normal imports.</p>
<p>And with that, that rounds up my exploration of Groovy source files on Github! It’s your turn to play with the dataset, and see if there are interesting findings to be unveiled! Did you find anything?</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Tale of a Groovy Spark in the Cloud</title><link>https://glaforge.dev/posts/2016/06/20/tale-of-a-groovy-spark-in-the-cloud/</link><pubDate>Mon, 20 Jun 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/06/20/tale-of-a-groovy-spark-in-the-cloud/</guid><description>&lt;p>As I recently &lt;a href="https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/">joined Google&lt;/a>’s developer advocacy team for &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a>, I thought I could have a little bit of fun with combining my passion for &lt;a href="http://groovy-lang.org/">Apache Groovy&lt;/a> with some cool cloudy stuff from Google! Incidentally, Paolo Di Tommaso tweeted about his own &lt;a href="https://twitter.com/PaoloDiTommaso/status/741290125947240448">experiments with using Groovy with Apache Spark&lt;/a>, and shared his &lt;a href="https://github.com/pditommaso/gspark/blob/master/src/main/groovy/org/apache/spark/examples/GroovySparkPi.groovy">code on Github&lt;/a>:&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-a17d06a4778a0f0b0305e4977327d2ec">
&lt;img src="https://glaforge.dev/img/spark-groovy/gspark-01.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-a17d06a4778a0f0b0305e4977327d2ec">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/spark-groovy/gspark-01.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>I thought that would be a nice fun first little project to try to use Groovy to run a Spark job on Google Cloud &lt;a href="https://cloud.google.com/dataproc/">Dataproc&lt;/a>! Dataproc manages Hadoop &amp;amp; Spark for you: it’s a service that provides managed Apache Hadoop, Apache Spark, Apache Pig and Apache Hive. You can easily process big datasets at low cost, control those costs by quickly creating managed clusters of any size and turning them off where you’re done. In addition, you can obviously use all the other Google Cloud Platform services and products from Dataproc (ie. store the big datasets in Google Cloud Storage, on HDFS, through BigQuery, etc.)&lt;/p></description><content:encoded>
<![CDATA[<p>As I recently <a href="https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/">joined Google</a>’s developer advocacy team for <a href="https://cloud.google.com/">Google Cloud Platform</a>, I thought I could have a little bit of fun with combining my passion for <a href="http://groovy-lang.org/">Apache Groovy</a> with some cool cloudy stuff from Google! Incidentally, Paolo Di Tommaso tweeted about his own <a href="https://twitter.com/PaoloDiTommaso/status/741290125947240448">experiments with using Groovy with Apache Spark</a>, and shared his <a href="https://github.com/pditommaso/gspark/blob/master/src/main/groovy/org/apache/spark/examples/GroovySparkPi.groovy">code on Github</a>:</p>
<p><figure>
  <a href="#img-a17d06a4778a0f0b0305e4977327d2ec">
    <img src="/img/spark-groovy/gspark-01.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a17d06a4778a0f0b0305e4977327d2ec">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-01.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>I thought that would be a nice fun first little project to try to use Groovy to run a Spark job on Google Cloud <a href="https://cloud.google.com/dataproc/">Dataproc</a>! Dataproc manages Hadoop &amp; Spark for you: it’s a service that provides managed Apache Hadoop, Apache Spark, Apache Pig and Apache Hive. You can easily process big datasets at low cost, control those costs by quickly creating managed clusters of any size and turning them off where you’re done. In addition, you can obviously use all the other Google Cloud Platform services and products from Dataproc (ie. store the big datasets in Google Cloud Storage, on HDFS, through BigQuery, etc.)</p>
<p>More concretely,, how do you run a Groovy job in Google Cloud Dataproc’s managed Spark service? Let’s see that in action!</p>
<p>To get started, I checked out Paolo’s <a href="https://github.com/pditommaso/gspark">samples</a> from Github, and I even groovy-fied the Pi calculation example (based on this <a href="https://en.wikipedia.org/wiki/Approximations_of_%CF%80#Summing_a_circle.27s_area">approach</a>) to make it a bit more idiomatic:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">package</span> org<span style="color:#666">.</span><span style="color:#4070a0">apache</span><span style="color:#666">.</span><span style="color:#4070a0">spark</span><span style="color:#666">.</span><span style="color:#4070a0">examples</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">groovy.transform.CompileStatic</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">org.apache.spark.SparkConf</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">org.apache.spark.api.java.JavaSparkContext</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">org.apache.spark.api.java.function.Function</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">org.apache.spark.api.java.function.Function2</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">scala.Function0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@CompileStatic</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">final</span> <span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">GroovySparkPi</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span> <span style="color:#007020;font-weight:bold">static</span> <span style="color:#902000">void</span> <span style="color:#06287e">main</span><span style="color:#666">(</span>String<span style="color:#666">[]</span> args<span style="color:#666">)</span> <span style="color:#007020;font-weight:bold">throws</span> Exception <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>   <span style="color:#902000">def</span> sparkConf <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> SparkConf<span style="color:#666">().</span><span style="color:#4070a0">setAppName</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;GroovySparkPi&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>   <span style="color:#902000">def</span> jsc <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> JavaSparkContext<span style="color:#666">(</span>sparkConf<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>   <span style="color:#902000">int</span> slices <span style="color:#666">=</span> <span style="color:#666">(</span>args<span style="color:#666">.</span><span style="color:#4070a0">length</span> <span style="color:#666">==</span> <span style="color:#40a070">1</span><span style="color:#666">)</span> <span style="color:#666">?</span> Integer<span style="color:#666">.</span><span style="color:#4070a0">parseInt</span><span style="color:#666">(</span>args<span style="color:#666">[</span><span style="color:#40a070">0</span><span style="color:#666">])</span> <span style="color:#666">:</span> <span style="color:#40a070">2</span>
</span></span><span style="display:flex;"><span>   <span style="color:#902000">int</span> n <span style="color:#666">=</span> <span style="color:#40a070">100000</span> <span style="color:#666">*</span> slices
</span></span><span style="display:flex;"><span>   <span style="color:#902000">def</span> dataSet <span style="color:#666">=</span> jsc<span style="color:#666">.</span><span style="color:#4070a0">parallelize</span><span style="color:#666">(</span><span style="color:#40a070">0</span><span style="color:#666">..</span>
</span></span><span style="display:flex;"><span>   <span style="color:#902000">def</span> mapper <span style="color:#666">=</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>     <span style="color:#902000">double</span> x <span style="color:#666">=</span> Math<span style="color:#666">.</span><span style="color:#4070a0">random</span><span style="color:#666">()</span> <span style="color:#666">*</span> <span style="color:#40a070">2</span> <span style="color:#666">-</span> <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>     <span style="color:#902000">double</span> y <span style="color:#666">=</span> Math<span style="color:#666">.</span><span style="color:#4070a0">random</span><span style="color:#666">()</span> <span style="color:#666">*</span> <span style="color:#40a070">2</span> <span style="color:#666">-</span> <span style="color:#40a070">1</span>
</span></span><span style="display:flex;"><span>     <span style="color:#007020;font-weight:bold">return</span> <span style="color:#666">(</span>x <span style="color:#666">*</span> x <span style="color:#666">+</span> y <span style="color:#666">*</span> y <span style="color:#666">&lt;</span> <span style="color:#40a070">1</span><span style="color:#666">)</span> <span style="color:#666">?</span> <span style="color:#40a070">1</span> <span style="color:#666">:</span> <span style="color:#40a070">0</span>
</span></span><span style="display:flex;"><span>   <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>   <span style="color:#902000">int</span> count <span style="color:#666">=</span> dataSet
</span></span><span style="display:flex;"><span>           <span style="color:#666">.</span><span style="color:#4070a0">map</span><span style="color:#666">(</span>mapper <span style="color:#007020;font-weight:bold">as</span> Function<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>           <span style="color:#666">.</span><span style="color:#4070a0">reduce</span><span style="color:#666">({</span><span style="color:#902000">int</span> a<span style="color:#666">,</span> <span style="color:#902000">int</span> b <span style="color:#666">-&gt;</span> a <span style="color:#666">+</span> b<span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">as</span> Function2<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>   println <span style="color:#4070a0">&#34;Pi is roughly ${4.0 * count / n}&#34;</span>
</span></span><span style="display:flex;"><span>   jsc<span style="color:#666">.</span><span style="color:#4070a0">stop</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span> <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span> 
</span></span></code></pre></div><p>You can also use a Groovy script instead of a full-blown class, but you need to make the script serializable with a little trick, by specifying a custom base script class. You need a custom Serializable Script:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">import</span> <span style="color:#0e84b5;font-weight:bold">groovy.transform.BaseScript</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@BaseScript</span> SerializableScript baseScript
</span></span></code></pre></div><p>And in your job script, you should specify this is your base script class with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">abstract</span> <span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">SerializableScript</span> <span style="color:#007020;font-weight:bold">extends</span> Script <span style="color:#007020;font-weight:bold">implements</span> Serializable <span style="color:#666">{}</span>
</span></span></code></pre></div><p>The project comes with a Gradle build file, so you can compile and build your project with the gradle jar command to quickly create a JAR archive.</p>
<p>Now let’s focus on the Cloud Dataproc part of the story! I basically simply followed the <a href="https://cloud.google.com/dataproc/quickstarts/quickstart-console">quickstart guide</a>. I used the Console (the UI web interface), but you could as well use the <a href="https://cloud.google.com/sdk/gcloud/">gcloud</a> command-line tool as well. You’ll need an account of course, and enable billing, as running Spark jobs on clusters can be potentially expensive, but don’t fear, there’s a <a href="https://cloud.google.com/free-trial">free trial</a> that you can take advantage of! You can also do some quick computation with the <a href="https://cloud.google.com/products/calculator">calculator</a> to estimate how much a certain workload will cost you. In my case, as a one time off job, this is a sub-dollar bill that I have to pay.</p>
<p>Let’s create a brand new project:</p>
<p><figure>
  <a href="#img-0bac8a10fdc73817f3b3883b112b9f5b">
    <img src="/img/spark-groovy/gspark-02.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-0bac8a10fdc73817f3b3883b112b9f5b">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-02.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>We’re going to create a Spark cluster, but we’ll need to enable the Compute Engine API for this to work, so head over to the hamburger menu, select the API manager item, and enable it:</p>
<p><figure>
  <a href="#img-6278d895917e2dcc8696529d29dd2aaf">
    <img src="/img/spark-groovy/gspark-04.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-6278d895917e2dcc8696529d29dd2aaf">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-04.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Select the Dataproc menu from the hamburger, which will allow you to create a brand new Spark cluster:</p>
<p><figure>
  <a href="#img-5a6f9845e3ca88984754108ee052e619">
    <img src="/img/spark-groovy/gspark-03.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-5a6f9845e3ca88984754108ee052e619">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-03.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Create a cluster as follows (the smallest one possible for our demo):</p>
<p><figure>
  <a href="#img-dfef9bb8f30e15f0b973ea50206b983e">
    <img src="/img/spark-groovy/gspark-05.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-dfef9bb8f30e15f0b973ea50206b983e">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-05.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Also, in case you have some heavy &amp; expensive workloads, for which it doesn’t matter much if they can be interrupted or not (and then relaunched later on), you could also use <a href="https://cloud.google.com/preemptible-vms/">Preemptible VMs</a> to further lower the cost.</p>
<p>We created a JAR archive for our Groovy Spark demo, and for the purpose of this demo, we’ll push the JAR into Google Cloud Storage, to create Spark jobs with this JAR (but there are other ways to push your job’s code automatically as well). From the menu again, go to Cloud Storage, and create a new bucket:</p>
<p><figure>
  <a href="#img-4960b98e4f6507b19ce6f5717c49760d">
    <img src="/img/spark-groovy/gspark-14.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-4960b98e4f6507b19ce6f5717c49760d">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-14.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Create a bucket with a name of your choice (we’ll need to remember it when creating the Spark jobs):</p>
<p><figure>
  <a href="#img-466b47d42731421e53d17f7cf132d407">
    <img src="/img/spark-groovy/gspark-15.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-466b47d42731421e53d17f7cf132d407">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-15.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Once this bucket is created, click on it, and then click on the “upload files” button, to upload your JAR file:</p>
<p><figure>
  <a href="#img-12a13df3927722513491419e78c4f036">
    <img src="/img/spark-groovy/gspark-16.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-12a13df3927722513491419e78c4f036">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-16.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>We can come back to the Dataproc section, clicking on the Jobs sub-menu to create a new job:</p>
<p><figure>
  <a href="#img-1ec7bafd091e486a4bbc969c79ea2998">
    <img src="/img/spark-groovy/gspark-26.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-1ec7bafd091e486a4bbc969c79ea2998">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-26.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>We’ll create a new job, using our recently created cluster. We’ll need to specify the location of the JAR containing our Spark job: we’ll use the URL <code>gs://groovy-spark-demo-jar/spark-groovy-1.1.jar</code>. The <code>gs://</code> part corresponds to the Google Cloud Storage protocol, as that’s where we’re hosting our JAR. Then groovy-spark-demo-jar/ corresponds to the name of the bucket we created, and then at the end, the name of the JAR file. We’ll use an argument of 1000 to specify the number of parallel computations of our Pi approximation algorithm we want to run:</p>
<p><figure>
  <a href="#img-a64f159aa14b3fe8efa1876888b8f206">
    <img src="/img/spark-groovy/gspark-27.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-a64f159aa14b3fe8efa1876888b8f206">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-27.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Click <code>Submit</code>, and here we go, our Groovy Spark job is running in the cloud on our 2-node cluster!</p>
<p><figure>
  <a href="#img-c7d6334ddcb1c0ceffad83100cc708fc">
    <img src="/img/spark-groovy/gspark-28.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c7d6334ddcb1c0ceffad83100cc708fc">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/spark-groovy/gspark-28.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>Just a bit of setup through the console, which you can also do from the command-line, and of course a bit of Groovy code to do the computation. Be sure to have a look at the <a href="https://cloud.google.com/dataproc/quickstarts/quickstart-console">quick start guide</a>, which gives more details than this blog post, and you can look at some other <a href="https://github.com/pditommaso/gspark/tree/master/src/main/groovy/org/apache/spark/examples">Groovy Spark samples</a> thanks to Paolo on his Github project.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Joining Google as a Developer Advocate for the Google Cloud Platform</title><link>https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/</link><pubDate>Thu, 02 Jun 2016 11:01:39 +0100</pubDate><guid>https://glaforge.dev/posts/2016/06/02/joining-google-as-a-developer-advocate-for-the-google-cloud-platform/</guid><description>&lt;p>The cat is out the bag: I&amp;rsquo;m joining Google on June 6th, as a &lt;strong>Developer Advocate&lt;/strong> for the &lt;strong>Google Cloud Platform&lt;/strong> team!&lt;/p>
&lt;p>My Groovy friends will likely remember when I launched &lt;a href="http://gaelyk.org/">Gaelyk&lt;/a>, a lightweight toolkit for developing Groovy apps on Google App Engine? Since then, I&amp;rsquo;ve always been a big fan of the &lt;a href="https://cloud.google.com/">Google Cloud Platform&lt;/a> (although it wasn&amp;rsquo;t called that way then) and followed the latest developments of the whole platform. And wohhh, so many new&lt;a href="https://cloud.google.com/products/"> services and products&lt;/a> have seen the light of day since my early experiments with App Engine! So there will be a lot to learn, a lot to do, and thus, a lot to advocate! &lt;/p></description><content:encoded>
<![CDATA[<p>The cat is out the bag: I&rsquo;m joining Google on June 6th, as a <strong>Developer Advocate</strong> for the <strong>Google Cloud Platform</strong> team!</p>
<p>My Groovy friends will likely remember when I launched <a href="http://gaelyk.org/">Gaelyk</a>, a lightweight toolkit for developing Groovy apps on Google App Engine? Since then, I&rsquo;ve always been a big fan of the <a href="https://cloud.google.com/">Google Cloud Platform</a> (although it wasn&rsquo;t called that way then) and followed the latest developments of the whole platform. And wohhh, so many new<a href="https://cloud.google.com/products/"> services and products</a> have seen the light of day since my early experiments with App Engine! So there will be a lot to learn, a lot to do, and thus, a lot to advocate! </p>
<p>I&rsquo;m really happy and excited to join the <a href="https://cloud.google.com/solutions/">Google Cloud Platform</a> team! I&rsquo;m looking forward to working with my new team, and join some good old Googler friends I&rsquo;ve came to know throughout my career.</p>
<p>I&rsquo;ve been really pleased to work with my friends at <a href="https://restlet.com/">Restlet</a> for the past year and a half, made friends, and learnt a lot along the way working with my colleagues. I wish them luck for their great <a href="https://restlet.com/platform/">Web API platform</a>, and I&rsquo;ll continue to follow their progress!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A Groovy journey in Open Source land (GR8Conf Europe)</title><link>https://glaforge.dev/talks/2016/06/02/a-groovy-journey-in-open-source-land-gr8conf-europe/</link><pubDate>Thu, 02 Jun 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2016/06/02/a-groovy-journey-in-open-source-land-gr8conf-europe/</guid><description>&lt;p>Direct live from &lt;a href="http://gr8conf.eu/#/">GR8Conf Europe 2016&lt;/a>, in Copenhagen, Denmark! This morning, I presented my latest &lt;a href="https://speakerdeck.com/glaforge/a-groovy-journey-in-open-source-land">update about the Apache Groovy history, and the latest developments&lt;/a> in the 2.4.x and future 2.5 branches.&lt;br />
Abstract:&lt;/p>
&lt;blockquote>
&lt;p>In dog years&amp;hellip; err&amp;hellip; Open Source years, the Groovy programming language project is a very mature and successful one, as its 12 million downloads a year can attest. The Groovy language is certainly the most widely deployed alternative language of the JVM today. But how do we go from a hobby night &amp;amp; week-end project to professionally company sponsored? And back again to hobby mode but joining the wider Apache Software Foundation community?&lt;/p></description><content:encoded>
<![CDATA[<p>Direct live from <a href="http://gr8conf.eu/#/">GR8Conf Europe 2016</a>, in Copenhagen, Denmark! This morning, I presented my latest <a href="https://speakerdeck.com/glaforge/a-groovy-journey-in-open-source-land">update about the Apache Groovy history, and the latest developments</a> in the 2.4.x and future 2.5 branches.<br />
Abstract:</p>
<blockquote>
<p>In dog years&hellip; err&hellip; Open Source years, the Groovy programming language project is a very mature and successful one, as its 12 million downloads a year can attest. The Groovy language is certainly the most widely deployed alternative language of the JVM today. But how do we go from a hobby night &amp; week-end project to professionally company sponsored? And back again to hobby mode but joining the wider Apache Software Foundation community?</p>
<p>Guillaume will guide you through the history of the project, its latest developments, and its recent news, outlining the importance of a community around an Open Source project.</p>
<p>Also, we&rsquo;ll discuss what it means to contribute, when it&rsquo;s your hobby or as a paid committer &ndash; what does it change? What it means to join the Apache community, what the impact of professional Open Source is, and more.</p></blockquote>
<p>And here are the slides:</p>
<script async class="speakerdeck-embed" data-id="beca9cd406a84e7c8f4a34374cdb458c" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Get in the flow! The API developer workflow!</title><link>https://glaforge.dev/talks/2016/05/26/get-in-the-flow-the-api-developer-workflow/</link><pubDate>Thu, 26 May 2016 00:02:00 +0200</pubDate><guid>https://glaforge.dev/talks/2016/05/26/get-in-the-flow-the-api-developer-workflow/</guid><description>&lt;p>What are the activities of the Web API developer? How API tooling should not get in the way of developer&amp;rsquo;s productivity? I presented a talk on this topic at the GlueCon conference:&lt;/p>
&lt;blockquote>
&lt;p>The API ecosystem provides powerful tools, online services and definition formats for designing, testing, running, or managing APIs. All share common purposes: improve our productivity when developing an API, allow us to collaborate more effectively, or share our creations with the world!&lt;/p></description><content:encoded>
<![CDATA[<p>What are the activities of the Web API developer? How API tooling should not get in the way of developer&rsquo;s productivity? I presented a talk on this topic at the GlueCon conference:</p>
<blockquote>
<p>The API ecosystem provides powerful tools, online services and definition formats for designing, testing, running, or managing APIs. All share common purposes: improve our productivity when developing an API, allow us to collaborate more effectively, or share our creations with the world!</p>
<p>But developers have already invented efficient tactics to streamline their development, gathered experience with and sharpened their tools of trade. The result is that the services or formats mentioned before can actually also get in their way, and interrupt their development flow, as they have to resort to get out of their routine and processes, to use them.</p>
<p>What can API tooling vendors do to reconcile the habits of developers with their tools? In this session, Guillaume Laforge, Restlet&rsquo;s Product Ninja &amp; Advocate, will talk about building, versioning &amp; dependency management of API artifacts, scenario &amp; conformance testing, API documentation, continuous integration, multi-environment continuous deployment, and team collaboration! Let’s get back into the development flow!</p></blockquote>
<script async class="speakerdeck-embed" data-id="66dea0c6069e42969fd55002089b11f7" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A five-sided prism polarizing Web API development</title><link>https://glaforge.dev/talks/2016/05/26/a-five-sided-prism-polarizing-web-api-development/</link><pubDate>Thu, 26 May 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2016/05/26/a-five-sided-prism-polarizing-web-api-development/</guid><description>&lt;p>At GlueCon, I presented about the 5-sided prism that polarizes Web API development:&lt;/p>
&lt;blockquote>
&lt;p>How do you tackle your API development? Are you diving head-first in the code to get something quickly out the door? Do you start by defining the API contract, that you&amp;rsquo;ll share between your teams and the consumers? Perhaps you prefer to describe your acceptance tests, explaining the behavior you expect from your API. But if you&amp;rsquo;re a storyteller, you&amp;rsquo;ll probably write some use cases, scenarios, to have a better feel for what your API is all about, and how your users will take advantage of it. Or simply, you already have data lying around that wants to set free, and be exposed restfully to the world.&lt;/p></description><content:encoded>
<![CDATA[<p>At GlueCon, I presented about the 5-sided prism that polarizes Web API development:</p>
<blockquote>
<p>How do you tackle your API development? Are you diving head-first in the code to get something quickly out the door? Do you start by defining the API contract, that you&rsquo;ll share between your teams and the consumers? Perhaps you prefer to describe your acceptance tests, explaining the behavior you expect from your API. But if you&rsquo;re a storyteller, you&rsquo;ll probably write some use cases, scenarios, to have a better feel for what your API is all about, and how your users will take advantage of it. Or simply, you already have data lying around that wants to set free, and be exposed restfully to the world.</p>
<p>In this session, Guillaume Laforge, Restlet&rsquo;s Product Ninja &amp; Advocate, will highlight different approaches to Web API development, along with their pros &amp; cons. Whether you&rsquo;re starting with code, a contract, tests, documentation, or data, you&rsquo;ll get a glimpse of light into the tasty book of API development recipes.</p></blockquote>
<script async class="speakerdeck-embed" data-id="52db3b6495bf4d7c8a298193fa350284" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A web API for each API consumer?</title><link>https://glaforge.dev/posts/2016/05/07/a-web-api-for-each-api-consumer/</link><pubDate>Sat, 07 May 2016 00:01:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/05/07/a-web-api-for-each-api-consumer/</guid><description>&lt;p>At our disposal, we have so many ways to interact with an API: from a mobile on iOS or Android, from a web application, or from other services or microservices. And all of them have different needs: one wants only a shallow overview of the data, while the other desires a detailed view of a certain resource and all its sub-resources. It&amp;rsquo;s becoming difficult to design an API that caters to the needs of those varied consumers.&lt;/p></description><content:encoded>
<![CDATA[<p>At our disposal, we have so many ways to interact with an API: from a mobile on iOS or Android, from a web application, or from other services or microservices. And all of them have different needs: one wants only a shallow overview of the data, while the other desires a detailed view of a certain resource and all its sub-resources. It&rsquo;s becoming difficult to design an API that caters to the needs of those varied consumers.</p>
<p>So what can we do? There&rsquo;s a trend around providing different API facades for each consumer, as Netflix does with its <a href="http://www.infoq.com/articles/api-facades">experience and ephemeral APIs</a>, or how Sam Newman described it in its <a href="http://samnewman.io/patterns/architectural/bff/">Backends for Frontends</a> pattern. But such an approach increases the maintenance burden and complexity, and might only really make sense for big enough teams.</p>
<p>Other approaches exist for customizing payloads for different consumers, without really providing as many derived API facades as you have API consumers. You can take advantage of the Prefer header, fields filtering, custom Mime media types, hypermedia, or Facebook&rsquo;s GraphQL approach.</p>
<p>I&rsquo;ve written an article for InfoQ that dives into this topic, and covers all those subjects: &ldquo;<a href="http://www.infoq.com/articles/api-facades">One API, many facades</a>&rdquo;.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>How far should API definition languages go?</title><link>https://glaforge.dev/posts/2016/05/07/how-far-should-api-definition-languages-go/</link><pubDate>Sat, 07 May 2016 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2016/05/07/how-far-should-api-definition-languages-go/</guid><description>&lt;p>I had the pleasure of writing an &lt;a href="http://nordicapis.com/how-far-should-api-definition-languages-go/">article&lt;/a> for Nordic APIs on Web API definition languages.&lt;/p>
&lt;p>If you&amp;rsquo;re into the world of Web APIs, you&amp;rsquo;ve probably heard of formats like &lt;a href="http://swagger.io/">Swagger&lt;/a>, &lt;a href="http://raml.org/">RAML&lt;/a> or &lt;a href="https://apiblueprint.org/">API Blueprint&lt;/a>. They allow developers to define the contract of the API, with its endpoints, its resources, its representations, allowed methods, the kind of payloads it understands, the status codes returned, and more.&lt;/p>
&lt;p>With the contract of your Web API, you can generate code for your backend implementation or client kits, documentation for publishing the details of your API for your API consumers. This contract becomes a key element of your API strategy: a contract between the frontend team and backend team to be sure to work on the same ground, between your tech team implementing the public API of your company and all the API consumers that will interact with it.&lt;/p></description><content:encoded>
<![CDATA[<p>I had the pleasure of writing an <a href="http://nordicapis.com/how-far-should-api-definition-languages-go/">article</a> for Nordic APIs on Web API definition languages.</p>
<p>If you&rsquo;re into the world of Web APIs, you&rsquo;ve probably heard of formats like <a href="http://swagger.io/">Swagger</a>, <a href="http://raml.org/">RAML</a> or <a href="https://apiblueprint.org/">API Blueprint</a>. They allow developers to define the contract of the API, with its endpoints, its resources, its representations, allowed methods, the kind of payloads it understands, the status codes returned, and more.</p>
<p>With the contract of your Web API, you can generate code for your backend implementation or client kits, documentation for publishing the details of your API for your API consumers. This contract becomes a key element of your API strategy: a contract between the frontend team and backend team to be sure to work on the same ground, between your tech team implementing the public API of your company and all the API consumers that will interact with it.</p>
<p>But API definition languages don&rsquo;t necessarily denote all the fineness of your API, in particular its business rules, or how to check that an implementation conforms to a contract or if the implementation follows the style guides of your company, what tests could exhibit the behavior of the API, and more. So the questions I asked myself was <a href="http://nordicapis.com/how-far-should-api-definition-languages-go/">how far should API definition languages go</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>How far should API definition languages go</title><link>https://glaforge.dev/posts/2016/04/27/how-far-should-api-definition-languages-go/</link><pubDate>Wed, 27 Apr 2016 14:46:34 +0100</pubDate><guid>https://glaforge.dev/posts/2016/04/27/how-far-should-api-definition-languages-go/</guid><description>&lt;p>&lt;figure>
&lt;a href="#img-f36b72b70a9c8aaf92f35990de96a187">
&lt;img src="https://nordicapis.com/wp-content/uploads/How-far-should-api-definition-languages-go-1.png"
alt="How-far-should-api-definition-languages-go"
/>
&lt;/a>
&lt;figcaption>How-far-should-api-definition-languages-go&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-f36b72b70a9c8aaf92f35990de96a187">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://nordicapis.com/wp-content/uploads/How-far-should-api-definition-languages-go-1.png"
alt="How-far-should-api-definition-languages-go"
/>
&lt;div class="lightbox-caption">How-far-should-api-definition-languages-go&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>The most common API definition languages we spot in the wild are &lt;a href="https://swagger.io/">Swagger&lt;/a> / &lt;a href="https://openapis.org/">OpenAPI Spec&lt;/a>, &lt;a href="https://raml.org/">RAML&lt;/a> and &lt;a href="https://apiblueprint.org/">API Blueprint&lt;/a>. All three let you define your endpoints, your resources, your query or path parameters, your headers, status codes, security schemes, and more.&lt;/p>
&lt;p>In a nutshell, these definition languages define the &lt;strong>structure&lt;/strong> of your API, and allow you to describe many &lt;strong>elements&lt;/strong>. As standards in the API industry evolve, however, their purpose and design are under continuous scrutiny. Specifically, the extensibility of API specifications with additional elements and feature sets comes into question.&lt;/p></description><content:encoded>
<![CDATA[<p><figure>
  <a href="#img-f36b72b70a9c8aaf92f35990de96a187">
    <img src="https://nordicapis.com/wp-content/uploads/How-far-should-api-definition-languages-go-1.png"
      alt="How-far-should-api-definition-languages-go"
       />
  </a>
  <figcaption>How-far-should-api-definition-languages-go</figcaption>
</figure>
<div class="lightbox" id="img-f36b72b70a9c8aaf92f35990de96a187">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="https://nordicapis.com/wp-content/uploads/How-far-should-api-definition-languages-go-1.png"
    alt="How-far-should-api-definition-languages-go"
     />
  <div class="lightbox-caption">How-far-should-api-definition-languages-go</div>
</div>
</p>
<p>The most common API definition languages we spot in the wild are <a href="https://swagger.io/">Swagger</a> / <a href="https://openapis.org/">OpenAPI Spec</a>, <a href="https://raml.org/">RAML</a> and <a href="https://apiblueprint.org/">API Blueprint</a>. All three let you define your endpoints, your resources, your query or path parameters, your headers, status codes, security schemes, and more.</p>
<p>In a nutshell, these definition languages define the <strong>structure</strong> of your API, and allow you to describe many <strong>elements</strong>. As standards in the API industry evolve, however, their purpose and design are under continuous scrutiny. Specifically, the extensibility of API specifications with additional elements and feature sets comes into question.</p>
<p>Recently, a discussion sparked on the <a href="https://nordicapis.com/open-api-initiative-means-api-space/">Open API Specification</a> issue tracker about the <a href="https://nordicapis.com/evolution-openapi-specification-openapi-mean-open-world/">Open World vs Closed World principles</a>. In a nutshell, the question is whether an API definition is supposed to describe an API fully (including, for example, the paths that you&rsquo;re not allowed to access, all the possible status codes, etc.) or whether it is just a best effort to describe what&rsquo;s usable and needed by most API consumers &mdash; not necessarily covering the whole scope of the API.</p>
<p>All the recent conversation has us thinking &mdash; <strong>how detailed should API definitions be</strong>?. One can dream up many other uses for an API definition language:</p>
<ul>
<li>API definitions could bring additional information or documentation, with a narrative and more metadata describing the flow of API calls, how the callers will be able to interact with the API.</li>
<li>API providers can bring such extra metadata for the purpose of API discovery, to give credits (about IP or copyright information), perhaps residing in external descriptors like <a href="https://apisjson.org/">APIs.json</a> (a format to help discover metadata about an API).</li>
<li>JSON payloads comply with JSON schemas, but data models could be defined regardless of the media types being chosen by the consumer, so that the consumer could actually request any output format, but still understand the intrinsic nature of the resources which are dealt with.</li>
<li>There are often common traits spreading across an API, for example how to do pagination, and some API definitions are able to factor this common aspect in a reusable and referenceable fashion.</li>
<li>Hypermedia APIs are not necessarily well covered in terms of modeling throughout the most common API specifications, there&rsquo;s certainly something to be improved here.</li>
<li>Another interesting topic is also about how to ensure that an API implementation conforms with its API definition, or follows some some good conventions or company guidelines. Tooling could be provided to help with this task.</li>
</ul>
<p><a href="https://raml.org/blogs/update-raml-10-current-status">RAML is approaching 1.0</a>, <a href="https://github.com/OAI/OpenAPI-Specification/issues?q=is%3Aissue+is%3Aopen+label%3A%22OpenAPI.Next+Proposal%22">OpenAPI Spec is being worked on</a> with a list of issues detailing what to expect, and <a href="https://github.com/apiaryio/api-blueprint/wiki/Roadmap">API Blueprint published its roadmap</a>. As our favorite specifications will soon see interesting evolutions, it&rsquo;s a great time to revisit the dialectic on API definition language design.</p>
<p>Let&rsquo;s look a little bit closer at some of the propositions above to see if API definitions provide solutions, and discuss if it&rsquo;s even the purpose of an API definition language to do these sort of things. What could we build into the next generation of API specifications to make them truly remarkable?</p>
<h2 id="api-storytelling-modeling-multi-step-transactions-within-documentation">API Storytelling: Modeling Multi-Step Transactions within Documentation</h2>
<p>API providers need to be storytellers. When you design an API for a user base, you want to tell them a story &mdash; how they access the API with an API key, how they interact with the API, and what the common traits for various resources are (pagination).</p>
<p>Often, to conduct a certain business use case you must issue several calls in a row: first you get the details of an order, then you ask for the details of the customer, and lastly perhaps their delivery address. So you <strong>chain calls</strong>, using elements of the response of the previous call to craft the subsequent request. Sometimes, you have different payloads, with or without embedded entities; an order call may contain all the details of the customer and its address, or it may not.</p>
<p>How can a provider explain the logical scenario of calls? A page published within the API documentation should describe such scenarios, and regroup logical series of calls together in a coherent story &mdash; the use cases of the API. Though you can provide descriptions of API parameters using current definition languages, there is no way to tie elements together in a cohesive <strong>workflow</strong>. An API definition with the power to tell <strong>API stories</strong> that describe common instances of multi-step transactions could be very powerful.</p>
<h2 id="test-scenarios">Test Scenarios</h2>
<p>Related to these use cases, having <strong>tests</strong> corresponding to those scenarios would be handy. For instance, <a href="https://nordicapis.com/10-continuous-integration-tools-spur-api-development/#dhc">DHC by Restlet</a> has its own test scenario definition format that you can use to run tests from a Maven plugin, or within your <a href="https://nordicapis.com/reach-devops-zen-with-these-continuous-integration-tools/">Continuous Integration pipeline</a> with the Jenkins server. Could such scenario tests be derived from the API definition, or embedded within the API definition itself? Interestingly, the <a href="https://github.com/apiaryio/api-blueprint/issues/21">API Blueprint roadmap lists scenarios and testing</a>, so this might be coming sooner than later.</p>
<h2 id="documentation-vs-specification">Documentation vs Specification</h2>
<p>It&rsquo;s easy to mix up the difference between the <strong>API definition format</strong> with the published <strong>API documentation</strong>. Though they <em>are</em> different entities, you can generate the documentation from the definition &mdash; there&rsquo;s no denying the two are intimately linked. If they were completely separate, API definition formats wouldn&rsquo;t even have any description tags at all, as the format would probably only be used for <a href="https://nordicapis.com/designing-apis-machines/">machine-consumption</a>, and not for <a href="https://nordicapis.com/designing-apis-humans/">human-consumption</a>.</p>
<p>Using API definitions, you can derive implementation for skeletons for your <a href="https://restlet.com/blog/2015/04/28/easy-client-sdk-and-server-skeleton-generation-for-your-apis/">API or client SDKs</a>. But you can also generate beautiful and comprehensive documentation: perhaps just static HTML documentation, or spiced up with sprinkles of JavaScript to provide an interactive playground to try out the API directly, or go full steam with hosting that definition in a full-blown <a href="https://nordicapis.com/beautiful-ui-design-for-api-developer-portals/">API developer portal</a> where you could add collaboration capabilities, versioning information, and more. As the end user documentation as well as <a href="https://nordicapis.com/description-agnostic-api-development-with-api-transformer/">SDKs and libraries</a> can be derived form the core API definition, the two are inherently linked.</p>
<h2 id="style-guide-conformance-and-tooling">Style Guide, Conformance and Tooling</h2>
<p>In designing an API, your company probably has already created a set of guidelines, or <strong>best practices</strong> to follow. For instance, look at <a href="https://github.com/paypal/api-standards/blob/master/api-style-guide.md">Paypal&rsquo;s API style guide</a>. The document describes the general structures of URIs, a mandatory namespace element, a paging method that should remain consistent, and more. Your own guidelines might specify naming conventions, like the use of camel-case.</p>
<p><strong>It would be handy to automatically assess if the API actually conforms to those guidelines</strong>. Maintaining this type of conformance could be part of the API definition itself, or exist as an external document that is referenced from the API definition. This could require a new format that describes these conventions rules, leading one to consider if such guidelines should be declaratively-defined, or if a scripting language could be used for more advanced validation needs.</p>
<p>Along with having a special guideline description file, tools would be needed to check that the implementation of the API really conforms to those guidelines! This tooling would likely not be part of an API definition format, but could accompany it, and be offered in various languages and technology stacks that anyone can feel at home with.</p>
<h2 id="hypermedia">Hypermedia</h2>
<p>One of the key tenets of the <strong>REST</strong> architectural style coined by Roy Fielding is hypermedia, with hypermedia as the engine of application state (HATEOAS). More APIs are conforming to this constraint, and several approaches exist to define those hypermedia links &mdash; <a href="https://nordicapis.com/designing-evolvable-apis-for-the-web-formats/#hypermediatypesforwebapis">we&rsquo;ve defined HAL, JSON-LD, and Siren hypermedia types in a past blog post</a>.</p>
<p>Even though the concept is popular, API languages often don&rsquo;t let you easily describe such hypermedia-driven APIs. Shouldn&rsquo;t they help users describe such metadata and hyperlinks? Zdenek Nemec of Apiary gave an example <a href="https://gist.github.com/zdne/988a54c03609655f47b7#file-rendered-md">implementation of HAL for an API</a>, using the new MSON modeling capabilities of API Blueprint. Perhaps in the near future, API languages will support a particular flag to denote what hypermedia approach the API is using.</p>
<h2 id="data-and-payload-modeling">Data and Payload Modeling</h2>
<p>Speaking of <a href="https://github.com/apiaryio/mson">MSON</a> (Markdown Syntax for Object Notation), it&rsquo;s also an interesting take on modeling your API payloads. With a convenient and readable format, which is neither JSON schema nor XML schema, you can describe what your data will look like, regardless of the underlying media type being used.</p>
<p>This beckons the question whether any possible payload can be supported that way. After all, esoteric APIs will probably not conform with JSON schema, XML schema, or MSON. There are likely going to be expressivity differences between them all. Should API definition languages be prescriptive, and limit to the 80/20 sane use cases of data modeling?</p>
<p>Another example is Apigee&rsquo;s recent <a href="https://apigee.com/about/blog/developer/rapier-cut-through-tedium-api-specification">Rapier API specification</a>, whose goal is to describe data-oriented APIs with entities and their relationships. Rapier might possibly be contributed to the OpenAPI Specification at some point, but there are clear limits in terms of expressivity.</p>
<h2 id="api-definition-extensions">API Definition Extensions</h2>
<p>Although API definition languages don&rsquo;t necessarily provide all those bells and whistles, an important aspect is <strong>extension</strong>. For example, <a href="https://swagger.io/specification/#vendorExtensions">Swagger / OpenAPI Spec provides the notion of extensions</a>. API designers have the ability to define custom fields (following a convention) within which they can stuff any data or metadata they want! So if you want to add guideline documentation pointers, or associated tests, you could very well add them as custom extensions. Then, your own tools can grok those specific extensions and deal with them however you like.</p>
<p>For instance, <a href="https://restlet.com/blog/2015/10/26/importing-and-translating-raml-api-definition/">tools translating from one API language to another</a> can store custom extensions that describe how to best do the translation, in particular in the case that one specification doesn&rsquo;t support certain metadata that the other supports. Let&rsquo;s keep in mind one downside of custom extensions though: They are not understood by all the tools and vendors, and are somewhat proprietary and non portable. So use at your own risk!</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>Right, so shoud API definition languages provide everything and the kitchen sink? If so, what&rsquo;s the threshold? Where do we put the cursor? Or &mdash; should an API definition simply stick to describing the various API elements, and then leverage a complementary format like <a href="https://apisjson.org/">APIs.json</a> to add and link to additional resources?</p>
<p>It is apparent that there are benefits to pushing API definition languages a bit further and providing more information than they do. Extensions are the way to go, as long as we define, publish and share such extensions in a somewhat standardized fashion. But some definitely feel the need for going further than what is offered today. What&rsquo;s your take on that? Where would you put the cursor?</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>One API, many facades?</title><link>https://glaforge.dev/posts/2016/03/13/one-api-many-facades/</link><pubDate>Sun, 13 Mar 2016 14:36:34 +0100</pubDate><guid>https://glaforge.dev/posts/2016/03/13/one-api-many-facades/</guid><description>&lt;p>An interesting trend is emerging in the world of Web APIs, with various engineers and companies advocating for dedicated APIs for each consumer with particular needs. Imagine a world where your system needs to expose not only one API for iOS, one API for Android, one for the website, and one for the AngularJS app front end, but also APIs for various set-top boxes and exotic mobile platforms or for third-party companies that call your API. Beyond any ideal design of your API, reality strikes back with the concrete and differing concerns of varied API consumers. You might need to optimize your API accordingly.&lt;/p></description><content:encoded>
<![CDATA[<p>An interesting trend is emerging in the world of Web APIs, with various engineers and companies advocating for dedicated APIs for each consumer with particular needs. Imagine a world where your system needs to expose not only one API for iOS, one API for Android, one for the website, and one for the AngularJS app front end, but also APIs for various set-top boxes and exotic mobile platforms or for third-party companies that call your API. Beyond any ideal design of your API, reality strikes back with the concrete and differing concerns of varied API consumers. You might need to optimize your API accordingly.</p>
<h2 id="experience-apis">Experience APIs</h2>
<p>On InfoQ, Jérôme Louvel (chief geek and co-founder of <a href="http://restlet.com/">Restlet</a>) interviewed Daniel Jacobson (vice president of edge engineering at <a href="http://www.netflix.com/">Netflix</a>) about Netflix&rsquo;s e<a href="http://www.infoq.com/news/2015/11/daniel-jacobson-ephemeral-apis">xperience APIs and ephemeral APIs</a>. Daniel&rsquo;s team is responsible for handling all traffic for signup, discovery, and playback of videos on devices around the world. With the concept of experience APIs, Netflix creates special APIs to handle optimized responses for a given requesting agent. With ephemeral APIs, Netflix engineers iteratively transition and evolve those experience APIs.</p>
<p>The goal of experience APIs is to solve problems Netflix has encountered while scaling its platform to more than 60 million consumers on dozens of devices with different characteristics. Offering such dedicated APIs allowed Netflix to provide the best user experiences possible across all devices and to optimize bandwidth according to a device&rsquo;s screen size for less latency and data consumption. It let Netflix engineers progress rapidly and independently from the core back-end teams, in isolation, with their own versioning scheme and orchestrating their own deployments.</p>
<p>At Netflix, dedicated teams take care of those experience APIs. It&rsquo;s not the central or core API team that is responsible for all the APIs derived for each and every possible client. There aren&rsquo;t many companies with the same scale as Netflix, and just because Netflix builds dedicated APIs for all API consumers doesn&rsquo;t mean it makes sense for your own context. For small shops, maintaining and evolving too many API front ends could be ineffective or even an anti-pattern, as the cost would be pretty high. Netflix had to build a special API platform to support this approach.</p>
<h2 id="microservices-architectures">Microservices architectures</h2>
<p>With the trend towards microservices-based architectures, the <a href="http://microservices.io/patterns/apigateway.html">API gateway</a> or API facade is making a resurgence. Your architecture is scattered among several small services and you need to have front-facing services responsible for exposing an API to consumers. So it&rsquo;s not all that surprising that by having many microservices you can also have multiple facades for your consumers.</p>
<p>A gateway or facade will let a consumer make just one call instead of forcing the consumer to make multiple calls to several underlying microservices. This alone is easier for the API consumer and the benefit increases with a smarter gateway or façade that takes advantage of caching (because multiple calls still need to be made), applies security concerns (authentication, authorization), or implements rules (rate limitation, IP filtering). The API provider can control how the consumers use its API.</p>
<p>Gateways, from vendors or built in-house like Netflix&rsquo;s, also add complementary value like edge services (an API infrastructure in the DMZ of the company can be used in novel and interesting ways, like how Netflix uses Zuul for multiregion resiliency) or pipelining or filter chaining (which help extract crosscutting concerns and implement enterprise-wide patterns).</p>
<h2 id="back-ends-for-front-ends">Back ends for front ends</h2>
<p>In a <a href="http://www.infoq.com/news/2015/12/bff-backend-frontend-pattern">recent article</a>, Sam Newman looks at this approach of dedicated consumer APIs as a pattern named &ldquo;<a href="http://samnewman.io/patterns/architectural/bff/">Backends for Frontends</a>&rdquo; (BfFs). Instead of a general-purpose API for all clients, you can have several BfFs: one for a Web front end, one for mobile clients (or even one for iOS and one for Android), and more.</p>
<p><a href="https://www.thoughtworks.com/insights/blog/bff-soundcloud">SoundCloud adopted the BfF pattern</a>, with API front ends for the iOS platform, the Android platform, the website, and for web-embedding. As is the case at Netflix, it seems that this technique works best when there are dedicated teams responsible for those front ends. If you have only one team taking care of the back end and its API, you&rsquo;d better not overload them with a large number of variations for different consumers.</p>
<p>Getting back to microservices, BfF can also make sense for migration: when migrating a monolith to microservices, one BfF can call into the monolith while other BfFs could call the new microservices instead, following the <a href="http://www.martinfowler.com/bliki/StranglerApplication.html">Strangler pattern</a> where you progressively move away from the legacy code to adopt newer evolutions.</p>
<p>A monolith is complex, easily accumulates technical debt, and mixes too many concerns at the same time, while microservices help you focus on one particular concern at a time. But microservices architectures also have drawbacks. You have to operate and orchestrate them, and they may have to evolve at a different pace from one big monolith. Maintaining consistency between all services in such distributed systems is not easy, either. Communication among many microservices might introduce additional latency because of communication delay. The consensus around the duplication and denormalization of data for each microservice can also complicate data management and consistency. <a href="http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html">Microservices are not a free lunch</a>, and you can read more on some microservices anti-patterns with Vijay Alagarasan&rsquo;s <a href="http://www.infoq.com/articles/seven-uservices-antipatterns">anti-patterns article</a>, or on Tareq Abedrabbo&rsquo;s &ldquo;<a href="https://www.opencredo.com/2014/11/19/7-deadly-sins-of-microservices/">The 7 Deadly Sins of Microservices</a>&rdquo;.</p>
<p>The deciding factor for choosing to use experience APIs or BfF could very well be having dedicated teams for them or not. If you&rsquo;re small and have only one team to take care of the back end and the front-facing or edge Web APIs, it might be more complicated for you to take care of the many variants (think maintenance costs) but if you&rsquo;re large enough, teams can more easily take ownership of these front-end APIs and evolve them at their own pace.</p>
<h2 id="apis-as-a-team-communication-pattern">APIs as a team-communication pattern</h2>
<p>While companies are organized as teams, I see more and more instances where developers are separated into front-end developers (whether Web or mobile) and back-end developers who implement the APIs needed for the Web or mobile devices. Web APIs have become central to the way projects are delivered: APIs are the contract that binds the different teams together and allows them to collaborate efficiently.</p>
<p>When developing an API that is going to be used by others, it&rsquo;s important not to break that contract. Often, frameworks and tools allow you to generate an API definition from the codebase &mdash; for example, with an annotation-driven approach where you label your endpoints, query parameters, etc. with annotations. But sometimes, even if your own test cases still pass, the smallest code refactoring could very well break the contract. Your codebase might be fine, but the refactoring might have broken the code of your API consumers. To collaborate more effectively, consider going with an API-contract-first approach and make sure your implementation still conforms with the shared agreement: the API definition. There are different API definition languages available and popular these days, like <a href="http://swagger.io/">Swagger</a> (<a href="https://github.com/OAI/OpenAPI-Specification">Open API specification</a>), <a href="http://raml.org/">RAML</a>, or <a href="https://apiblueprint.org/">API Blueprint</a>. Pick one you&rsquo;re comfortable with.</p>
<p>Working with an API definition has a few advantages. First of all, it should make it more difficult to break compatibility as your implementation has to conform to the API definition. Secondly, API definitions are pretty well equipped in terms of tooling. From API definitions, you can generate client SDKs that your API consumers can integrate in their projects to call your API or even server skeletons to generate the initial implementation of your service. You can also create mocks of your APIs, which developers can easily call while the underlying API is being built, without juggling the different development cycles of the producers and consumers of APIs. Each team can work at its own pace! However, it&rsquo;s not just about code or compatibility but also about documentation. API-definition languages also help when documenting your API, with nicely generated documentation that shows the various endpoints, query parameters, etc., as well as (sometimes) offering an interactive console, which allows you to easily craft calls to the API.</p>
<h2 id="different-payloads-for-different-consumers">Different payloads for different consumers</h2>
<p>Adopting an API-contract-first approach is certainly helpful and provides benefits, but what can you do when different clients have different API needs? Particularly, if you don&rsquo;t have the luxury of dedicated teams taking ownership of different API facades, how can you make your API meet the needs of all your API consumers?</p>
<p>In a recent article on InfoQ, Jean-Jacques Dubray explained why he <a href="http://www.infoq.com/articles/no-more-mvc-frameworks">stopped using MVC frameworks</a>. In the introduction, he explained how mobile or front-end developers frequently asked for APIs tailored for their UI needs, regardless of a sound data model for the underlying business concepts. The state-action-model (SAM) pattern that Dubray described nicely supports the BfF approach. SAM is a new, reactive functional pattern which simplifies fronted architectures by clearly separating the business logic from the effects, in particular decoupling back-end APIs from the view. As the state and model are separate from actions and views, the actions can be specific for a given front end or not appear at all: it&rsquo;s up to you to decide where to put the cursor. You may also generate the state representation or view from the central back end or by those intermediary facades.</p>
<p>A website or single-page application might need to display a detailed view of a product and all its reviews, but perhaps a mobile device will only show the product details and its rating, letting the mobile user tap to load the reviews afterwards. Depending on the UI, the flow, the actions available, the level of detail, and the entities retrieved might be different. Typically, you&rsquo;d like to diminish the number of API calls to retrieve data on a mobile device because of connectivity and bandwidth constraints, and you want the payload returned to contain only what&rsquo;s required and nothing more. But this doesn&rsquo;t matter that much for the Web front end, and with asynchronous calls, you&rsquo;re totally fine with loading more content or resources lazily. In either case, APIs should obviously respond rapidly, and have a good service-level agreement. But what are the options for delivering multiple customized APIs to different consumers?</p>
<h2 id="specific-endpoints-query-parameters-and-fields-filtering">Specific endpoints, query parameters, and fields filtering</h2>
<p>A basic approach could be to provide different endpoints (<code>/api/mobile/movie</code> versus <code>/api/web/movie</code>) or even simply query parameters (<code>/api/movie?format=full</code> or <code>/api/movie?format=mobile</code>), but there are perhaps more elegant solutions.</p>
<p>Similar to query parameters, your API might be able to customize returned payloads by letting the consumer decide which fields he or she wants, like: <code>/api/movie?fields=title,kind,rating</code>, or <code>/api/movie?exclude=actors</code>.</p>
<p>With fields filtering, you may decide also if you want to get related resources in response: <code>/api/movie?includes=actors.name</code>.</p>
<h2 id="custom-mime-media-types">Custom MIME media types</h2>
<p>As the implementor of the API, you have options. You might decide not to offer any customization at all! Consumers will either have to go with what you offer or wrap your API inside their own facade into which they&rsquo;ve built the customization they want. But since you&rsquo;re a great person, you could offer them profiles: you can be creative with media types and offer a leaner or richer payload depending on the media type a consumer requests. For instance, if you look at the <a href="https://developer.github.com/v3/media/">GitHub API</a>, you&rsquo;ll notice types like: <code>application/vnd.github.v3.full+json</code></p>
<p>Along with a &ldquo;full&rdquo; profile that offers the whole payload and related entities, you can provide a &ldquo;mobile&rdquo; variant, and perhaps a &ldquo;minimal&rdquo; one too.</p>
<p>The API consumer makes a call that requests the media type that fits his/her use case the most.</p>
<h2 id="prefer-header">Prefer header</h2>
<p>Irakli Nadareishvili wrote about <a href="http://www.freshblurbs.com/blog/2015/06/25/api-representations-prefer.html">client-optimized resource representations in APIs</a>, mentioning a lesser-known header field: the Prefer header (<a href="https://tools.ietf.org/html/rfc7240">RFC 7240</a>).</p>
<p>As with custom media types, a client would request a certain profile using the Prefer header: using <code>Prefer: return=mobile</code> would have the API reply with a customized payload and the header <code>Preference-Applied: return=mobile</code>. Note that the Vary header should also mention that the Prefer header is available.</p>
<p>Depending on whether you, the API developer, want to be in charge of deciding what kind of payloads you support, you might like the custom media type, the Prefer header, or dedicated endpoints. If you want to let clients decide more explicitly what kind of fields and relationships to retrieve, you could opt for field filtering or query parameters.</p>
<h2 id="graphql">GraphQL</h2>
<p>With its <a href="https://facebook.github.io/react/">React</a> view framework, Facebook <a href="https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html">introduced</a> developers to <a href="https://facebook.github.io/graphql/">GraphQL</a>. Here, consumers are in total control of what they&rsquo;ll receive: the fields and relationships. The consumer issues a call that specifies what the return payload should look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-graphql" data-lang="graphql"><span style="display:flex;"><span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>user(id:<span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">3500401</span>)<span style="color:#bbb"> </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>id,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>name,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>isViewerFriend,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>profilePicture(size:<span style="color:#bbb"> </span><span style="color:#0e84b5;font-weight:bold">50</span>)<span style="color:#bbb">  </span>{<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>uri,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>width,<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">      </span>height<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">    </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">  </span>}<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb"></span>}<span style="color:#bbb">
</span></span></span></code></pre></div><p>And the API should reply with the following payload:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;user&#34;</span> : {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;id&#34;</span>: <span style="color:#40a070">3500401</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;Jing Chen&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;isViewerFriend&#34;</span>: <span style="color:#007020;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;profilePicture&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;uri&#34;</span>: <span style="color:#4070a0">&#34;http://someurl.cdn/pic.jpg&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;width&#34;</span>: <span style="color:#40a070">50</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;height&#34;</span>: <span style="color:#40a070">50</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>GraphQL is at the same time a query and a description of what you&rsquo;d like the answer to this query to be. GraphQL lets API consumers totally control what they&rsquo;ll get in return, offering the highest level of flexibility.</p>
<p>A similar approach exists in specifications like OData, which lets you customize the payloads with <a href="http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value">
  <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>e</mi><mi>l</mi><mi>e</mi><mi>c</mi><mi>t</mi><mo separator="true">,</mo></mrow><annotation encoding="application/x-tex">select, </annotation></semantics></math></span>

expand, and $value parameters</a>. But OData has not really caught on and might be on the verge of abandonment; <a href="http://www.ben-morris.com/netflix-has-abandoned-odata-does-the-standard-have-a-future-without-an-ecosystem/">Netflix and eBay stopped supporting OData</a> a while ago. That said, other actors like Microsoft and SalesForce do still support it.</p>
<h2 id="hypermedia-apis">Hypermedia APIs</h2>
<p>One last option to explore is hypermedia APIs. When thinking of hypermedia APIs, you often think of all the additional hyperlinks that clutter responses and that could easily double the payload size. Payload size and number of calls really matter to a mobile device. Despite that, it&rsquo;s important to think of hypermedia through HATEOAS (Hypermedia as the engine of application state), a core tenet of REST APIs that is often overlooked. It&rsquo;s about the capabilities offered by the API. A consumer will have access to related resources, but links offered through those hypermedia relations can also be about giving different profiles to choose from, like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;_links&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;self&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>: <span style="color:#4070a0">&#34;/movie/123&#34;</span> },
</span></span><span style="display:flex;"><span>        <span style="color:#062873;font-weight:bold">&#34;mobile&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>: <span style="color:#4070a0">&#34;/m/movie/123&#34;</span> },
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Additionally, certain hypermedia approaches fully embrace the notion of embedding related entities. Hydra, HAL, and SIREN provide the ability to embed sub-entities, so that you could retrieve a particular movie and the embedded list of all the actors in that movie.</p>
<p>From an article on <a href="http://sookocheff.com/post/api/on-choosing-a-hypermedia-format/">how to choose a hypermedia format</a>, Kevin Sookocheff gives an example showing how accessing a &ldquo;player&rsquo;s list of friends&rdquo; resource also embeds the actual representations of those friends and not just links to those individual resources, thus eliminating calls to each friend resource:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#062873;font-weight:bold">&#34;_links&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;self&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#4070a0">&#34;https://api.example.com/player/1234567890/friends&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;size&#34;</span>: <span style="color:#4070a0">&#34;2&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#062873;font-weight:bold">&#34;_embedded&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#062873;font-weight:bold">&#34;player&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;_links&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;self&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#4070a0">&#34;https://api.example.com/player/1895638109&#34;</span> },
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;friends&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#4070a0">&#34;https://api.example.com/player/1895638109/friends&#34;</span> }
</span></span><span style="display:flex;"><span>          },
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;playerId&#34;</span>: <span style="color:#4070a0">&#34;1895638109&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;Sheldon Dong&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#062873;font-weight:bold">&#34;_links&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;self&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#4070a0">&#34;https://api.example.com/player/8371023509&#34;</span> },
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;friends&#34;</span>: { <span style="color:#062873;font-weight:bold">&#34;href&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#4070a0">&#34;https://api.example.com/player/8371023509/friends&#34;</span> }
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;playerId&#34;</span>: <span style="color:#4070a0">&#34;8371023509&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#062873;font-weight:bold">&#34;name&#34;</span>: <span style="color:#4070a0">&#34;Martin Liu&#34;</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="summary">Summary</h2>
<p>Web APIs increasingly have several kinds of consumers with different needs. Microservice architectures can encourage us to deploy fine-grained API facades for those needs (the so-called experience APIs or BfF patterns), but this can become an anti-pattern if you have too many distinct consumers to please, especially if you&rsquo;ve got only a small team to take care of all those front ends.</p>
<p>Be sure to do the math! Before going one way or another, you have to study the cost of your options and whether or not you can support them. <a href="http://www.ebpml.org/blog15/2013/11/understanding-the-costs-of-versioning-an-api-or-a-service/">Creating different variants of an API has a cost</a>, for the implementor as well as for the consumer, that depends on the adopted strategy. Also, once you&rsquo;ve unleashed your API and given it to its consumers, perhaps it&rsquo;s also time to rethink  and refactor this API, as maybe you didn&rsquo;t take those special device or consumer requirements well enough into account during the design phase.</p>
<p>If you have dedicated teams for these API facades, then it&rsquo;s an option to consider. When you don&rsquo;t have that luxury, there are other ways to customize payloads for your consumers without the induced complexity, with simple tricks like field filtering or the Prefer header up to full-blown solutions like custom media types or specifications like GraphQL.</p>
<p>But you don&rsquo;t necessarily need to fire the big guns, and could opt for a middle path: one main, full API plus one or two variants for mobile devices, and you are likely going to meet the requirements of all your consumers. Consider including a pinch of field filtering, and everybody will be happy with your APIs!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy default params to avoid one-argument methods being called without params</title><link>https://glaforge.dev/posts/2016/01/08/groovy-default-params-to-avoid-one-argument-methods-being-called-without-params/</link><pubDate>Fri, 08 Jan 2016 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2016/01/08/groovy-default-params-to-avoid-one-argument-methods-being-called-without-params/</guid><description>&lt;p>Recently, I saw an interesting &lt;a href="https://twitter.com/djsmith42/status/679018096334675968">tweet&lt;/a>, mentioning a JavaScript trick using default parameters to make a parameter mandatory.&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-14f0971da0b32c675f86d38763e29374">
&lt;img src="https://glaforge.dev/img/misc/default-param-mandatory-javascript-trick.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-14f0971da0b32c675f86d38763e29374">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/default-param-mandatory-javascript-trick.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>In a language like &lt;a href="http://www.groovy-lang.org">Apache Groovy&lt;/a>, when statically compiled, you&amp;rsquo;d get a compilation error if you forgot a parameter, because the signature couldn&amp;rsquo;t be found by the compiler. In dynamic mode, you&amp;rsquo;d get a runtime error though, with a MissingMethodException (and the error message should give you a hint as to which method you should actually call instead).&lt;/p></description><content:encoded>
<![CDATA[<p>Recently, I saw an interesting <a href="https://twitter.com/djsmith42/status/679018096334675968">tweet</a>, mentioning a JavaScript trick using default parameters to make a parameter mandatory.</p>
<p><figure>
  <a href="#img-14f0971da0b32c675f86d38763e29374">
    <img src="/img/misc/default-param-mandatory-javascript-trick.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-14f0971da0b32c675f86d38763e29374">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/default-param-mandatory-javascript-trick.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p>In a language like <a href="http://www.groovy-lang.org">Apache Groovy</a>, when statically compiled, you&rsquo;d get a compilation error if you forgot a parameter, because the signature couldn&rsquo;t be found by the compiler. In dynamic mode, you&rsquo;d get a runtime error though, with a MissingMethodException (and the error message should give you a hint as to which method you should actually call instead).</p>
<p>But there&rsquo;s a particular case of the Groovy method dispatch that&rsquo;s a bit special (and actually something we might be removing at some point in a breaking version of the language, but it&rsquo;s been there since 1.0). When you have single-argument methods, you&rsquo;re allowed to call those methods without passing a parameter! And the parameter is filled simply with null. So you might have made a mistake in your code, forgetting to pass an actual parameter value, but you&rsquo;d get neither a compilation error nor a runtime exception, but a null value.</p>
<p>So I thought about that JavaScript trick, and adapted to this situation, to ensure that you can&rsquo;t call a one-argument method without an argument.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>String <span style="color:#06287e">up</span><span style="color:#666">(</span>String s<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    s<span style="color:#666">?.</span><span style="color:#4070a0">toUpperCase</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">assert</span> <span style="color:#06287e">up</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;groovy&#39;</span><span style="color:#666">)</span> <span style="color:#666">==</span> <span style="color:#4070a0">&#39;GROOVY&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// a strange aspect of Groovy is that 
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// you can call a one-argument method without passing any actual argument
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// as if you were passing null, as in up(null)
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">assert</span> <span style="color:#06287e">up</span><span style="color:#666">()</span> <span style="color:#666">==</span> <span style="color:#007020;font-weight:bold">null</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// let&#39;s use the JavaScript trick with mandatory default params:
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// https://twitter.com/djsmith42/status/679018096334675968
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span>String <span style="color:#06287e">up2</span><span style="color:#666">(</span>String s <span style="color:#666">=</span> mandatory<span style="color:#666">(</span><span style="color:#4070a0">&#39;s&#39;</span><span style="color:#666">))</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    s<span style="color:#666">?.</span><span style="color:#4070a0">toUpperCase</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#902000">void</span> <span style="color:#06287e">mandatory</span><span style="color:#666">(</span>String paramName<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">throw</span> <span style="color:#007020;font-weight:bold">new</span> <span style="color:#06287e">Exception</span><span style="color:#666">(</span><span style="color:#4070a0">&#34;Please provide an actual value for &#39;$paramName&#39;&#34;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">assert</span> <span style="color:#06287e">up2</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;groovy&#39;</span><span style="color:#666">)</span> <span style="color:#666">==</span> <span style="color:#4070a0">&#39;GROOVY&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    up2<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">catch</span><span style="color:#666">(</span>emAll<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">assert</span> emAll<span style="color:#666">.</span><span style="color:#4070a0">message</span> <span style="color:#666">==</span> <span style="color:#4070a0">&#34;Please provide an actual value for &#39;s&#39;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic">// another approach with using a closure call as default value
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>
</span></span><span style="display:flex;"><span>String <span style="color:#06287e">up3</span><span style="color:#666">(</span>String s <span style="color:#666">=</span> <span style="color:#666">{</span> <span style="color:#666">-&gt;</span> <span style="color:#007020;font-weight:bold">throw</span> <span style="color:#007020;font-weight:bold">new</span> Exception<span style="color:#666">(</span><span style="color:#4070a0">&#34;Please provide an actual value for &#39;s&#39;&#34;</span><span style="color:#666">)</span> <span style="color:#666">}()</span> <span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    s<span style="color:#666">?.</span><span style="color:#4070a0">toUpperCase</span><span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">assert</span> <span style="color:#06287e">up3</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;groovy&#39;</span><span style="color:#666">)</span> <span style="color:#666">==</span> <span style="color:#4070a0">&#39;GROOVY&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">try</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    up3<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span> <span style="color:#007020;font-weight:bold">catch</span><span style="color:#666">(</span>emAll<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#007020;font-weight:bold">assert</span> emAll<span style="color:#666">.</span><span style="color:#4070a0">message</span> <span style="color:#666">==</span> <span style="color:#4070a0">&#34;Please provide an actual value for &#39;s&#39;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>I&rsquo;ve also pushed that example on the <a href="http://groovyconsole.appspot.com/script/5094328489738240">Groovy Web Console</a>, if you wanna play with it.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #77</title><link>https://glaforge.dev/posts/2015/09/13/groovy-weekly-77/</link><pubDate>Sun, 13 Sep 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/09/13/groovy-weekly-77/</guid><description>&lt;p>It’s literally the eve of &lt;a href="http://springone2gx.com/">SpringOne2GX&lt;/a>, in Washington DC! I’m flying tomorrow, for 9+ hours above the Atlantic, to gather with the cool kids of the Groovy ecosystem. I’m impatient to see some of my fellow readers and Groovy users around there.&lt;/p>
&lt;p>And let me wish the &lt;a href="http://griffon-user.74797.x6.nabble.com/Happy-Birthday-Griffon-td48.html">Griffon framework and its team a Groovy birthday&lt;/a>!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://ratpack.io/versions/1.0.0-rc-2">Ratpack 1.0-rc-2&lt;/a>, a second release candidate&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gradle/status/640174805958856705">Gradle 2.7-rc-2&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grailsframework/status/639813351988662272">Grails 3.0.5&lt;/a> released with the new Web API profile&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grailsframework/status/641593634530443264">Grails 3.0.6&lt;/a> released&lt;/li>
&lt;li>&lt;a href="http://spring.io/blog/2015/09/04/spring-boot-1-3-0-m5-available-now">Spring Boot 1.3.0.M5&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://sergiodelamo.es/grails-3-webapi-vs-web-profiles/">Grails 3 Web profile vs the new Web API profile&lt;/a>&lt;/li>
&lt;li>How to &lt;a href="https://medium.com/@benorama/how-to-deploy-grails-3-app-to-aws-elastic-beanstalk-with-gradle-and-travis-318d084c0f7d">deploy a Grails 3 app to AWS Beanstalk and CloudFront CDN&lt;/a> by Benoit Hédiard&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Groovy Goodness
&lt;ul>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2015/09/groovy-goodness-removing-elements-from.html">Removing elements from a collection&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2015/09/groovy-goodness-inspect-method-returns.html">Inspect method returns nicely formatted object values&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2015/09/groovy-goodness-operator-overloading-in.html">Operator overloading in reverse&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Spocklight
&lt;ul>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2015/09/spocklight-only-run-specs-based-on.html">Only run specs based on conditions&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2015/09/spocklight-undo-metaclass-changes.html">Undo MetaClass changes&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Philip Heath writes about &lt;a href="http://www.thattestingguy.com/spock-the-logical-choice-for-testing-java/">Spock, the logical choice for testing&lt;/a>&lt;/li>
&lt;li>Paul Gross&amp;rsquo; team has &lt;a href="https://www.pgrs.net/2015/09/01/migrating-from-gradle-to-bazel/">migrated from Gradle to Bazel&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="screencasts">Screencasts&lt;/h2>
&lt;ul>
&lt;li>Screencast of &lt;a href="https://twitter.com/netbeans/status/640283206789562368">NetBeans&amp;rsquo; Gradle integration&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="interviews">Interviews&lt;/h2>
&lt;ul>
&lt;li>An interview by JaxEnter of Dierk König in German, where he says that &lt;a href="https://jaxenter.de/groovy-ist-ein-pionier-fuer-neue-ideen-26638">Groovy is a pioneer language&lt;/a> where innovative changes help shape the future of Java and other languages&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-list-posts">Mailing-list posts&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://griffon-user.74797.x6.nabble.com/Happy-Birthday-Griffon-td48.html">Griffon celebrates the 7th anniversary&lt;/a> of the project!&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>The Mars rover has a state called &amp;ldquo;&lt;a href="https://twitter.com/mittie/status/641371571915988992">Solar Groovy&lt;/a>&amp;rdquo; noticed Dierk König&lt;/li>
&lt;li>Marco Vermeulen is excited about the upcoming &lt;a href="https://twitter.com/marc0der/status/640609521531011073">new website for SDKmanager&lt;/a> (aka GVM), based off the Groovy website code and look&lt;/li>
&lt;li>&lt;a href="https://twitter.com/sdkmanager/status/639812197833703424">Grails 3.0.5&lt;/a> available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/sdkmanager/status/641589310110740480">Grails 3.0.6&lt;/a> available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/sdkmanager/status/639620148576559104">Spring Boot 1.3.0.M5&lt;/a> available in GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/sdkmanager/status/640167344459939841">Gradle 2.7-rc-2&lt;/a> available through GVM&lt;/li>
&lt;li>Bertrand Goetzman working on a Groovy/Grails training is using a &lt;a href="https://twitter.com/bgoetzmann/status/639558195476869120">Groovy DSL with Grain to generate dynamic HTML documentation&lt;/a> from Markdown documents&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/grainframework/status/641197859644305408">Gradle plugin for the Grain&lt;/a> static site generator is available on the Gradle plugin portal&lt;/li>
&lt;li>Benoit Hédiard created a &lt;a href="https://twitter.com/benorama/status/641512438416953344">French-speaking channel in the Grails Slack&lt;/a> community&lt;/li>
&lt;li>Roman Shaposhnik is reminded he loves Groovy when looking at how it can make the &lt;a href="https://twitter.com/rhatr/status/641762008786825217">Apache BigTop project DSL&lt;/a> easy to grok&lt;/li>
&lt;li>Matt Sheehan brings our attention to the &lt;a href="https://twitter.com/sheehan00/status/641622945043980288">new auto-generated Groovy DSL documentation for the Jenkins Job DSL&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>Jacob Aae Mikkelsen&amp;rsquo;s &lt;a href="http://grydeske.net/news/show/108">Grails Diary&lt;/a> week 36&lt;/li>
&lt;/ul>
&lt;h2 id="jobs">Jobs&lt;/h2>
&lt;ul>
&lt;li>SmartThings is hiring a cloud engineer with &lt;a href="https://twitter.com/danveloper/status/639622402205425665">Ratpack experience&lt;/a>&lt;/li>
&lt;li>Gilles Laborderie is recruiting for a &lt;a href="https://twitter.com/g_laborderie/status/639465486116331520">Grails job in Paris&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>MrHaki&amp;rsquo;s &lt;a href="http://mrhaki.blogspot.fr/2015/09/spocklight-notebook-is-updated.html">Spocklight notebook updated&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>Only &lt;a href="https://twitter.com/pledbrook/status/639811956765913090">a few days left before the end of the Groovy/Grails eXchange call for paper&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/JavaOneConf/status/641000355740942336">Guillaume Laforge is a JavaOne featured speaker&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge &lt;a href="https://twitter.com/MrsCaroline_C/status/641332943802269696">relaunched the Paris Groovy Grails User Group&lt;/a> introducing talks on the Grails 3 novelties and Groovy in a VoIP context&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>It’s literally the eve of <a href="http://springone2gx.com/">SpringOne2GX</a>, in Washington DC! I’m flying tomorrow, for 9+ hours above the Atlantic, to gather with the cool kids of the Groovy ecosystem. I’m impatient to see some of my fellow readers and Groovy users around there.</p>
<p>And let me wish the <a href="http://griffon-user.74797.x6.nabble.com/Happy-Birthday-Griffon-td48.html">Griffon framework and its team a Groovy birthday</a>!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://ratpack.io/versions/1.0.0-rc-2">Ratpack 1.0-rc-2</a>, a second release candidate</li>
<li><a href="https://twitter.com/gradle/status/640174805958856705">Gradle 2.7-rc-2</a> released</li>
<li><a href="https://twitter.com/grailsframework/status/639813351988662272">Grails 3.0.5</a> released with the new Web API profile</li>
<li><a href="https://twitter.com/grailsframework/status/641593634530443264">Grails 3.0.6</a> released</li>
<li><a href="http://spring.io/blog/2015/09/04/spring-boot-1-3-0-m5-available-now">Spring Boot 1.3.0.M5</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://sergiodelamo.es/grails-3-webapi-vs-web-profiles/">Grails 3 Web profile vs the new Web API profile</a></li>
<li>How to <a href="https://medium.com/@benorama/how-to-deploy-grails-3-app-to-aws-elastic-beanstalk-with-gradle-and-travis-318d084c0f7d">deploy a Grails 3 app to AWS Beanstalk and CloudFront CDN</a> by Benoit Hédiard</li>
<li>MrHaki&rsquo;s Groovy Goodness
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/09/groovy-goodness-removing-elements-from.html">Removing elements from a collection</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/09/groovy-goodness-inspect-method-returns.html">Inspect method returns nicely formatted object values</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/09/groovy-goodness-operator-overloading-in.html">Operator overloading in reverse</a></li>
</ul>
</li>
<li>MrHaki&rsquo;s Spocklight
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/09/spocklight-only-run-specs-based-on.html">Only run specs based on conditions</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/09/spocklight-undo-metaclass-changes.html">Undo MetaClass changes</a></li>
</ul>
</li>
<li>Philip Heath writes about <a href="http://www.thattestingguy.com/spock-the-logical-choice-for-testing-java/">Spock, the logical choice for testing</a></li>
<li>Paul Gross&rsquo; team has <a href="https://www.pgrs.net/2015/09/01/migrating-from-gradle-to-bazel/">migrated from Gradle to Bazel</a></li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Screencast of <a href="https://twitter.com/netbeans/status/640283206789562368">NetBeans&rsquo; Gradle integration</a></li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>An interview by JaxEnter of Dierk König in German, where he says that <a href="https://jaxenter.de/groovy-ist-ein-pionier-fuer-neue-ideen-26638">Groovy is a pioneer language</a> where innovative changes help shape the future of Java and other languages</li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li><a href="http://griffon-user.74797.x6.nabble.com/Happy-Birthday-Griffon-td48.html">Griffon celebrates the 7th anniversary</a> of the project!</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>The Mars rover has a state called &ldquo;<a href="https://twitter.com/mittie/status/641371571915988992">Solar Groovy</a>&rdquo; noticed Dierk König</li>
<li>Marco Vermeulen is excited about the upcoming <a href="https://twitter.com/marc0der/status/640609521531011073">new website for SDKmanager</a> (aka GVM), based off the Groovy website code and look</li>
<li><a href="https://twitter.com/sdkmanager/status/639812197833703424">Grails 3.0.5</a> available on GVM</li>
<li><a href="https://twitter.com/sdkmanager/status/641589310110740480">Grails 3.0.6</a> available on GVM</li>
<li><a href="https://twitter.com/sdkmanager/status/639620148576559104">Spring Boot 1.3.0.M5</a> available in GVM</li>
<li><a href="https://twitter.com/sdkmanager/status/640167344459939841">Gradle 2.7-rc-2</a> available through GVM</li>
<li>Bertrand Goetzman working on a Groovy/Grails training is using a <a href="https://twitter.com/bgoetzmann/status/639558195476869120">Groovy DSL with Grain to generate dynamic HTML documentation</a> from Markdown documents</li>
<li>The <a href="https://twitter.com/grainframework/status/641197859644305408">Gradle plugin for the Grain</a> static site generator is available on the Gradle plugin portal</li>
<li>Benoit Hédiard created a <a href="https://twitter.com/benorama/status/641512438416953344">French-speaking channel in the Grails Slack</a> community</li>
<li>Roman Shaposhnik is reminded he loves Groovy when looking at how it can make the <a href="https://twitter.com/rhatr/status/641762008786825217">Apache BigTop project DSL</a> easy to grok</li>
<li>Matt Sheehan brings our attention to the <a href="https://twitter.com/sheehan00/status/641622945043980288">new auto-generated Groovy DSL documentation for the Jenkins Job DSL</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/108">Grails Diary</a> week 36</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>SmartThings is hiring a cloud engineer with <a href="https://twitter.com/danveloper/status/639622402205425665">Ratpack experience</a></li>
<li>Gilles Laborderie is recruiting for a <a href="https://twitter.com/g_laborderie/status/639465486116331520">Grails job in Paris</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>MrHaki&rsquo;s <a href="http://mrhaki.blogspot.fr/2015/09/spocklight-notebook-is-updated.html">Spocklight notebook updated</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Only <a href="https://twitter.com/pledbrook/status/639811956765913090">a few days left before the end of the Groovy/Grails eXchange call for paper</a></li>
<li><a href="https://twitter.com/JavaOneConf/status/641000355740942336">Guillaume Laforge is a JavaOne featured speaker</a></li>
<li>Guillaume Laforge <a href="https://twitter.com/MrsCaroline_C/status/641332943802269696">relaunched the Paris Groovy Grails User Group</a> introducing talks on the Grails 3 novelties and Groovy in a VoIP context</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #76</title><link>https://glaforge.dev/posts/2015/09/01/groovy-weekly-76/</link><pubDate>Tue, 01 Sep 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/09/01/groovy-weekly-76/</guid><description>&lt;p>Is it really already September? Time flies so fast in the Groovy ecosystem, and although the northern hemisphere was taking some Summer vacation breaks (at least that’s what I did!) there was quite a bit of interesting content published!&lt;/p>
&lt;p>Regarding events, after successful and busy Greach, GR8Conf US/EU conferences, we’re getting ready for September’s &lt;a href="http://springone2gx.com/">SpringOne2GX&lt;/a> in Washington! There’s still time to register and join the Groovy ecosystem affictionados! And I’m really eager to seeing you there.&lt;/p></description><content:encoded>
<![CDATA[<p>Is it really already September? Time flies so fast in the Groovy ecosystem, and although the northern hemisphere was taking some Summer vacation breaks (at least that’s what I did!) there was quite a bit of interesting content published!</p>
<p>Regarding events, after successful and busy Greach, GR8Conf US/EU conferences, we’re getting ready for September’s <a href="http://springone2gx.com/">SpringOne2GX</a> in Washington! There’s still time to register and join the Groovy ecosystem affictionados! And I’m really eager to seeing you there.</p>
<p>The Call for Papers for <a href="https://twitter.com/farjinaarad/status/633979624733601792">Grails India</a> and <a href="https://twitter.com/pledbrook/status/637255460643581952">Groovy Grails eXchange</a> London are also open, for those who wish to submit presentations.</p>
<p>If you’re using Gradle for build automation, check out all the videos of the <a href="http://gradle.org/category/video/">presentations of the recent Gradle Summit</a> which should all be available online.</p>
<p>Happy 3rd birthday to <a href="http://grooscript.org/">GrooScript</a>!</p>
<p>Congrats to the Griffon team for the <a href="https://twitter.com/theaviary/status/638545829826023424">2.4</a> release and Ratpack for its <a href="http://ratpack.io/versions/1.0.0-rc-1">RC</a> before 1.0!</p>
<p>And check out the updated books from MrHaki on <a href="http://mrhaki.blogspot.fr/2015/08/groovy-goodness-notebook-is-updated.html">Groovy</a>, <a href="http://mrhaki.blogspot.fr/2015/08/grails-goodness-notebook-updated.html">Grails</a> and <a href="http://mrhaki.blogspot.fr/2015/08/gradle-goodness-notebook-updated.html">Gradle</a>, and Duncan Dickinson’s updated <a href="https://leanpub.com/groovytutorial">Groovy 2 tutorial</a> on LeanPub.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/theaviary/status/638545829826023424">Griffon 2.4.0</a> released</li>
<li><a href="http://ratpack.io/versions/1.0.0-rc-1">Ratpack 1.0.0-rc-1</a> released, the last step before the final 1.0!</li>
<li><a href="https://discuss.gradle.org/t/gradle-2-7-rc-1-is-now-available-for-testing/11382">Gradle 2.7-rc-1</a> available for testing</li>
<li><a href="http://www.grengine.ch/">Grengine 1.0.4</a> released</li>
<li><a href="https://groups.google.com/forum/#!topic/spockframework/dkw0uHmOpuM">Spock Reports 1.2.6</a> released</li>
<li><a href="https://twitter.com/grainframework/status/637193955512315904">Grain 0.6.6</a> released</li>
<li><a href="https://groups.google.com/forum/#!topic/geb-user/ac_DvJzNr0A">Geb 0.12.2</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Keegan Witt, Groovy committer and author of the GMavenPlus Maven plugin, <a href="http://wittykeegan.blogspot.fr/2015/08/a-maven-plugin-developers-thoughts-on.html">contrasts Maven and Gradle</a></li>
<li><a href="http://wittykeegan.blogspot.dk/2015/07/custom-configuration-script-asts.html">Custom configuration script ASTs</a> by Keegan Witt</li>
<li>The tale of Cédric Champeau <a href="http://melix.github.io/blog/2015/08/permgenleak.html">fighting PermGen leaks</a> with Gradle, Groovy, CodeNarc</li>
<li>Kyle Boon shares part 2 of his <a href="http://kyleboon.org/blog/2015/08/14/zero-to-ratpack-part-2/">Zero to Ratpack</a> series</li>
<li>Ted Vinke writes about <a href="https://tedvinke.wordpress.com/2015/08/15/groovy-weekend-collections-splitting-or-collating-a-list/">splitting and collating lists in Groovy</a></li>
<li><a href="http://jkutner.github.io/2015/08/17/deploy-gradle-ratpack-heroku-docker.html">Deploying Gradle apps to Heroku with Docker</a> by Joe Kutner</li>
<li>André Steingreß <a href="http://blog.andresteingress.com/2015/08/21/grails-gpars-and-hibernate/">mixes GPars, Grails and Hibernate</a> together</li>
<li>Andrés Almiray on <a href="http://www.jroller.com/aalmiray/entry/griffon_webfont_icons_on_desktop">using webfonts icons in Griffon</a></li>
<li>Andrés Almirays shows how to <a href="http://www.jroller.com/aalmiray/entry/griffon_stacking_webfont_icons">stack web font icons in Griffon</a></li>
<li><a href="https://objectpartners.com/2015/08/13/sharing-grails-hal-and-json-renderers/">Sharing Grails HAL and JSON renderers</a> by Patrick Double</li>
<li>Søren Berg Glasius demonstrates using <a href="http://sbglasius.tumblr.com/post/126896829137/digetauthwithrestclientbuilder">Grails&rsquo; RestClientBuilder for digest auth</a></li>
<li>Android and <a href="http://tech.ticketmaster.com/2015/07/24/2015-year-of-the-android/">Gradle at TicketMaster</a></li>
<li>MrHaki&rsquo;s Spocklight
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/08/spocklight-optimize-run-order-test.html">optimize run order test methods</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/08/spocklight-include-or-exclude.html">include or exclude specifications based on class or interface</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/08/spocklight-including-or-excluding.html">including or excluding specifications based on annotations</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/09/spocklight-auto-cleanup-resources.html">auto cleanup resources</a></li>
</ul>
</li>
<li>MrHaki&rsquo;s Gradle goodness:
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/08/gradle-goodness-quickly-open-test.html">quickly open test report in IntelliJ IDEA</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/08/gradle-goodness-using-continuous-build.html">using the continuous build feature</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/08/show-logback-configuration-status-with.html">Show Logback configuration status with Groovy configuration</a> by MrHaki</li>
</ul>
</li>
<li>IntelliJ IDEA 15 EAP features <a href="http://blog.jetbrains.com/idea/2015/08/intellij-idea-15-eap-groovy-builder-ast-transformation-support/">support for the Groovy @Builder</a> AST transformation</li>
<li>A <a href="http://codereview.stackexchange.com/questions/96824/a-groovy-election">Groovy election</a>, a Groovy implementation of a programming challenge</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>You can find all the <a href="http://gradle.org/category/video/">Gradle Summit 2015 videos</a> on the Gradle website</li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>A post from Russel Winder on the <a href="http://groovy.329449.n5.nabble.com/What-is-the-status-of-Gpars-tp5727389p5727403.html">status of the GPars project</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>The <a href="https://twitter.com/grooscript/status/633728580309118976">Grooscript project is looking for feedback</a> if you&rsquo;re using it in your company</li>
<li>Happy <a href="https://twitter.com/jfrancoleza/status/636794537751511040">3rd birthday to GrooScript</a>!</li>
<li>Jenn Strater heard of <a href="https://twitter.com/JennStrater/status/637813075370995713">Groovy undergraduate courses</a> but wonders if universities use Groovy at graduate or research levels</li>
<li>Delivering a Groovy course reminds Dierk König how much <a href="https://twitter.com/mittie/status/638659372789465088">he loves the language for its practical nature</a></li>
<li>The IntelliJ IDEA developers are looking for <a href="https://twitter.com/grailsframework/status/638455090068586496">feedback for the Grails 3 support in IDEA</a></li>
<li>Ken Kousen&rsquo;s <a href="https://twitter.com/kenkousen/status/638399868315246593">O&rsquo;Reilly learning path is half the price</a> till tomorrow</li>
<li>The <a href="https://twitter.com/GebFramework/status/633702178813800449">Geb website has been updated with the latest version of Ratpack</a></li>
<li>Jen Stratter shares her <a href="https://twitter.com/JennStrater/status/634718571919220736">ReGinA selfie</a>! Have you shared yours?</li>
<li>Eugene Kamenev&rsquo;s <a href="https://twitter.com/eugenekamenev/status/634613381354401792">OrientDB Groovy entity mapper was added to the OrientDB documentation</a></li>
<li>The <a href="https://twitter.com/golo_lang/status/637171327091253248">Golo programming language is switching to Gradle</a> for its build system</li>
<li>Are you following the <a href="https://twitter.com/Gradlephant/status/634768293673484289">GradlEphant twitter account</a>?</li>
<li>Ted Naleid is working on <a href="https://twitter.com/tednaleid/status/635647401483612160">Spackle, a replacement of the build-test-data Grails plugin</a></li>
<li>Peter Ledbrook reminds that if the new <a href="https://twitter.com/pledbrook/status/638251832721154048">Gradle publishing plugin</a> hasn&rsquo;t worked for you, its source code is available on Github</li>
<li>Lukas Bradley highlighted <a href="https://twitter.com/lukasbradley/status/638368466542555137">Gradle&rsquo;s new hourly 1:1 consulting offer</a></li>
<li>Peter Ledbrook notes that to <a href="https://twitter.com/pledbrook/status/638302250633428993">understand the background of Gradle&rsquo;s new model</a> and an overview of how it works, one should watch Luke Daley&rsquo;s presentation at Gradle Summit</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Peter Ledbrook started a <a href="https://twitter.com/pledbrook/status/636533443980083200">Groovy syntax quick reference guide</a> and is interested in feedback</li>
<li>The August 2015 <a href="http://gradle.org/august-2015-newsletter/">Gradle newsletter</a></li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/105">Grails Diary</a> week 33</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/106">Grails Diary</a> week 34</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/107">Grails Diary</a> week 35</li>
<li>A new release of the <a href="http://groovycalamari.com/">Groovy Calamari column</a></li>
<li>Graeme Rocher shares the <a href="https://github.com/grails/grails-core/wiki/Roadmap">Grails 3.1 roadmap</a></li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li><a href="https://twitter.com/gradle/status/634741590771245056">Gradle Inc. is hiring</a> services and R&amp;D engineers</li>
<li><a href="https://twitter.com/jeffscottbrown/status/638430257670828032">OCI is hiring</a> for the Grails team</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Ben Boggess shows how to use <a href="https://twitter.com/benboggess/status/636987319900352512">Groovy&rsquo;s combination() method to create all the combinations of variables in a Spock specification</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Duncan Dickinson updated his <a href="https://leanpub.com/groovytutorial">Groovy 2 tutorial book</a> on LeanPub</li>
<li>MrHaki published an update of the <a href="http://mrhaki.blogspot.fr/2015/08/groovy-goodness-notebook-is-updated.html">Groovy Goodness Notebook</a></li>
<li>MrHaki published an update to the <a href="http://mrhaki.blogspot.fr/2015/08/gradle-goodness-notebook-updated.html">Gradle Goodness Notebook</a></li>
<li>MrHaki published an update to the <a href="http://mrhaki.blogspot.fr/2015/08/grails-goodness-notebook-updated.html">Grails Goodness Notebook</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Audio and video of the <a href="https://twitter.com/groovypodcast/status/634410345814228995">Groovy Podcast episode 18</a> are available</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/pledbrook/status/637255460643581952">Groovy Grails eXchange Call for Paper</a> is open</li>
<li><a href="https://twitter.com/paulk_asert/status/633959787286233088">Paul King will be speaking at ApacheCon Europe</a> in Budapest about the awesome parts of Groovy</li>
<li><a href="https://twitter.com/restlet/status/634736986272727040">Guillaume Laforge is a featured speaker at JavaOne</a> 2015</li>
<li>Dierk König announces some upcoming <a href="https://twitter.com/mittie/status/638584905694359553">Groovy &amp; Grails courses in Essen</a>, Germany</li>
<li>The <a href="https://twitter.com/farjinaarad/status/633979624733601792">Call for Paper for GrailsConf India</a> 2016 is open</li>
<li>The <a href="https://docs.google.com/forms/d/1ACjCltUodSwZ2u_0X22IeCujV3UGEq3oqp-dMmqmBeo/viewform">Paris Groovy user group is back on September 8th</a> in Paris, to talk about the novelties of Grails 3 and Groovy in the context of telephony and VoIP</li>
<li>An avalanche of SpringOne2GX talks announcements
<ul>
<li>Cédric Champeau will dive into the <a href="https://twitter.com/springone2gx/status/638365711815938051">Groovy compiler internals</a> at SpringOne2GX</li>
<li>Dan Woods will be speaking about <a href="https://twitter.com/danveloper/status/637662873930571776">Ratpack</a></li>
<li>Owen Rubel will speak about <a href="https://twitter.com/owenrubel/status/638771331295760384">scalable APIs with Grails</a></li>
<li>Allison Figus will be speaking about <a href="https://twitter.com/springone2gx/status/638426112394072064">functional automated testing with Geb</a></li>
<li>Michael Plöd will speak about <a href="https://twitter.com/springone2gx/status/638743172806406145">migrating from Grails 2 to 3</a></li>
<li>Guillaume Laforge will be speaking about <a href="https://twitter.com/springone2gx/status/638758243599171584">Groovy and REST</a></li>
<li>Guillaume Laforge will be presenting <a href="https://twitter.com/springone2gx/status/638410980284731392">Groovy with Style</a></li>
<li>Iván López will be speaking about <a href="https://twitter.com/ilopmar/status/638628617740681216">Spock</a></li>
<li>Jorge Franco Leza will be speaking about <a href="https://twitter.com/springone2gx/status/638395906753064961">GrooScript</a></li>
</ul>
</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #75</title><link>https://glaforge.dev/posts/2015/08/15/groovy-weekly-75/</link><pubDate>Sat, 15 Aug 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/08/15/groovy-weekly-75/</guid><description>&lt;p>The not-so-weekly Groovy column is back for a summer edition on the shores of the Atlantic ocean (at least, that’s where I am now!) and there was a lot to catch up with in terms of news!&lt;/p>
&lt;p>Ratpack is almost at 1.0, only a couple of weeks to go for the mythical release, whereas Gradle arrived in version 2.6, and some updates to the two main Grails lines were out too.&lt;/p></description><content:encoded>
<![CDATA[<p>The not-so-weekly Groovy column is back for a summer edition on the shores of the Atlantic ocean (at least, that’s where I am now!) and there was a lot to catch up with in terms of news!</p>
<p>Ratpack is almost at 1.0, only a couple of weeks to go for the mythical release, whereas Gradle arrived in version 2.6, and some updates to the two main Grails lines were out too.</p>
<p>You’ll notice lots of delightful content from GR8Conf US 2015! So many talks to watch (and slides to look at), if you didn’t get a chance to attend the conference.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://ratpack.io/versions/0.9.19">Ratpack 0.9.19</a> released, the last milestone before 1.0!</li>
<li><a href="https://discuss.gradle.org/t/gradle-2-6-released/11092">Gradle 2.6</a> released</li>
<li><a href="https://github.com/grails/grails-core/releases/tag/v3.0.4">Grails 3.0.4</a> released</li>
<li><a href="https://github.com/grails/grails-core/releases/tag/v2.5.1">Grails 2.5.1</a> released</li>
<li><a href="https://twitter.com/GebFramework/status/628475584092377089">Geb 0.12.1</a> released</li>
<li><a href="http://grooscript.org/">GrooScript 1.2.0</a> released</li>
<li><a href="https://twitter.com/grainframework/status/628862549916839936">Grain 0.6.5</a> released with improved Asciidoctor support</li>
<li><a href="https://twitter.com/keeganwitt/status/630918051425353728">Windows installer for Groovy 2.4.4</a> made available by Keegan Witt</li>
<li><a href="https://twitter.com/grooscript/status/631151629845327872">Grails GrooScript 1.1.1</a> plugin released</li>
<li><a href="https://twitter.com/eugenekamenev/status/626623038931488768">OrientDB Groovy 0.1.0</a> released</li>
<li><a href="https://twitter.com/sothmann/status/625756046674497536">TypeScript Gradle plugin 1.5</a> released with all the new TypeScript 1.5 compiler options</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Open-sourcing the <a href="https://engineering.linkedin.com/hadoop/open-sourcing-linkedin-gradle-plugin-and-dsl-apache-hadoop">LinkedIn Gradle plugin and DSL for Apache Hadoop</a> by Alex Bain</li>
<li><a href="https://www.linkedin.com/pulse/groovy-javas-gateway-drug-owen-rubel">Groovy: Java&rsquo;s gateway drug</a>, by Owen Rubel</li>
<li>A <a href="https://www.linkedin.com/pulse/scala-vs-groovy-functional-programming-showdown-owen-rubel">Scala vs Groovy functional programming showdown</a> by Owen Rubel</li>
<li>A <a href="https://github.com/kdabir/awesome-groovy">curated list of awesome Groovy libraries</a>, frameworks, resources, collected by Kunal Dabir</li>
<li><a href="http://gradle.org/maven_vs_gradle/">Feature matrix comparison between Gradle and Maven</a>, on Gradle&rsquo;s site</li>
<li>On InfoQ, Abel Avram writes about <a href="http://www.infoq.com/news/2015/07/gradle-2-5">Gradle 2.5&rsquo;s continuous build</a> feature</li>
<li>Julien Ponge, creator of the Golo dynamic language, <a href="https://julien.ponge.org/blog/an-experiment-in-maven-to-gradle-migration/">compares a non trivial Maven build and a Gradle approach</a></li>
<li><a href="https://www.accelebrate.com/blog/have-a-groovy-spring/">Have a Groovy Spring</a> by Ken Kousen</li>
<li><a href="http://kyleboon.org/blog/2015/08/05/zero-to-ratpack/">Zero to Ratpack</a>, by Kyle Boon, on more concrete use cases &amp; examples for Ratpack</li>
<li><a href="http://kyleboon.org/blog/2015/07/18/stubbing-service-interactions-when-testing-microservices/">Stubbing external service interactions using Ratpack</a> by Kyle Boon</li>
<li><a href="http://www.infoq.com/articles/Ratpack-and-Spring-Boot">Build high performance JVM microservices with Ratpack and Spring Boot</a> by Dan Woods on InfoQ</li>
<li><a href="https://objectpartners.com/2015/08/03/gr8conf-us-2015-conference-recap/">GR8Conf US 2015 recap</a> by Craig Atkinson from Object Partners</li>
<li>Peter Ledbrook on <a href="http://blog.cacoethes.co.uk/groovyandgrails/exploring-grails-3">exploring Grails 3</a></li>
<li><a href="http://jworks.nl/2015/07/31/getting-started-with-asciidoctor/">Getting started with Asciidoctor and Gradle</a> by Erik Pragt</li>
<li><a href="http://www.tothenew.com/blog/grails-filter-at-top-of-filter-invocation-chain/">Grails Filter at top of Filter invocation chain</a> by Sandeep Poonia</li>
<li>Lovin Saini on <a href="http://www.tothenew.com/blog/when-details-in-principal-object-of-spring-security-are-not-sufficient/">when details in principal object of Spring Security in Grails are not sufficient</a></li>
<li><a href="http://www.tothenew.com/blog/integrating-amazon-s3-in-grails-application/">Integrating Amazon S3 in a Grails application</a> by Komal Jain</li>
<li><a href="http://www.tothenew.com/blog/creating-android-application-with-groovy/">Creating Android applications with Groovy</a> by Simranjit Kour</li>
<li><a href="http://www.tothenew.com/blog/closure-delegate-using-groovy-with-method-and-decorating-code-with-multiple-assignments/">Closure delegate using Groovy “with” method and decorating code with multiple assignments</a> by Tarun Pareek</li>
<li><a href="https://dzone.com/articles/upgrading-a-grails-app-from-version-137-to-version">Upgrading a Grails app from version 1.3.7 to 2.4.4</a> by Manvendra Singh on DZone</li>
<li>André Steingreß blogged about <a href="http://blog.andresteingress.com/2015/08/14/grails-hibernate-jdbc-connection/">reconnecting JDBC connections with Grails</a></li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>A slice of <a href="http://devops.com/2015/08/13/slice-groovys-hip-use-jfrog-artifactory-bintray-gradle-teamcity/">Groovy&rsquo;s hip use of JFrog&rsquo;s Artifactory &amp; Bintray, and of Gradle &amp; TeamCity</a>, from an interview of Guillaume Laforge on DevOps.com</li>
<li><a href="https://www.voxxed.com/blog/2015/08/ratpack-the-java-8-web-framework-for-independent-thinkers/">Luke Daley interviewed for Voxxed about Ratpack</a>, the “Java 8 web framework for independent thinkers”</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>GR8Conf US 2015
<ul>
<li><a href="https://www.youtube.com/watch?v=Xeh2IExt8rA">Vert.x</a> presented by Ryan Applegate (<a href="https://speakerdeck.com/rappleg/getting-groovy-with-vert-dot-x">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=2tXoPTZmB_4">RESTful Web Services in Grails 3</a> presented by Jenn Strater (<a href="http://jlstrater.github.io/restful-grails3/#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=NuL3QkSSXy8">Practical and Stupidly Impractical Groovy DSLs</a> presented by Craig Burke (<a href="http://www.craigburke.com/practical-groovy-dsl/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=QWnuhlBwNh8">Richer Data History with Event Sourcing</a> presented by Stephen Pember</li>
<li><a href="https://www.youtube.com/watch?v=ugy4wSEy_A0">Groovy Goodness</a>, presented by Hubert Klein Ikkink (<a href="https://github.com/mrhaki/gr8conf2015us/tree/master/groovy-goodness">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=7G6z7Lqxqtw">Groovy DevOps in the Cloud</a> presented by Andrey Adamovich (<a href="http://www.slideshare.net/aestasit/groovy-devops-in-the-cloud-for-gr8conf-us-2015">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=i6WOnN8uJdM">GVM: The Groovy enVironment Manager</a>, presented by Marco Vermeulen (<a href="http://marcovermeulen.github.io/gvm-talk/#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=SSK_JaBacE0">Jenkins + Groovy with the Job DSL Plugin</a> presented by Matt Sheehan (<a href="http://sheehan.github.io/job-dsl-slides/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=iodl0QXkVag">Asset Pipeline</a> presented by David Estes (<a href="https://github.com/GR8conf/GR8ConfUS2015/tree/master/asset-pipeline">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=Ey7sGdmCX2Y">The support tools necessary to effectively build a Microservice Architecture</a> presented by Cameron Fieber from Netflix</li>
<li><a href="https://www.youtube.com/watch?v=yk_VlKUDKMA">Microservices: The Right Way</a> presented by Dan Woods (<a href="http://www.slideshare.net/danveloper/microservices-the-right-way">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=jxGQrJxjF-s">Idiomatic Spock</a> presented by Rob Fletcher (<a href="https://github.com/robfletcher/spock-katas">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=9I8fyX7qnoU">Automated Strategies for deploying Grails from Dev to Prod</a> presented by Eric Helgeson (<a href="https://github.com/erichelgeson/GR8ConfUS2015/blob/585814b5abd383deee512a2f4767d305fa364e18/presentations/Automation%20strategies%20for%20deploying%20your%20Grails%20app%20from%20Dev%20to%20Prod/Automation-Strategies-Eric-Helgeson-gr8-2015.pdf">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=YdrqW22kHS4">Grails Mocking on Steroids</a> presented by Christian Oestreich (<a href="http://grails-plugin-consortium.github.io/mocking-on-steroids-presentation/?full#1">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=rpepX441E5g">Geb Functional Testing Unleashed</a> presented by Craig Atkinson (<a href="http://craigatk.github.io/geb/#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=qWPMYysLgis">Nebula: Netflix&rsquo;s OSS Gradle Plugins</a> presented by Rob Spieldenner (<a href="https://speakerdeck.com/rspieldenner/nebula-netflixs-oss-gradle-plugins">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=e5erGrh7n_s">Securing Ratpack</a> presented by Jeff Beck (<a href="http://beckje01.com/talks/gr8us-2015-sec-ratpack.html#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=rU0kCAyxSfQ">Cassandra and Grails</a> presented by Jeff Beck (<a href="http://beckje01.com/talks/gr8us-2015-cassandra-grails.html#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=onwoLeHRxl0">Spock on Android</a> by Andrew Reitz (<a href="http://andrewreitz.com/gr8conf-us-spock-android-2015/#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=ymbvs4qoQJU">Groovy and Scala: Friends or Foes</a> presented by Marco Vermeulen (<a href="http://marcovermeulen.github.io/groovy-and-scala-talk/#/">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=U_p2L47gvgk">Getting Groovy on Android</a> presented by Andrew Reitz (<a href="https://onedrive.live.com/view.aspx?resid=29181012CB08492E!882&amp;ithint=file%2cpptx&amp;app=PowerPoint&amp;authkey=!AGwH3YhuhRS1P2s">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=vY5mYcw3XHk">Source to Deployment with Gradle and Docker</a> presented by John Engelman (<a href="https://speakerdeck.com/johnrengelman/source-to-deployment-with-gradle-and-docker">slides</a>)</li>
<li><a href="https://www.youtube.com/watch?v=-49ng22DHkw">Learn how to get Groovy on Google Glass and Android Wear</a> presented by Ryan Vanderwerf</li>
<li><a href="https://www.youtube.com/watch?v=NBv5y2O1oTY">Little Did He Know&hellip;</a> presented by Burt Beckwith</li>
<li><a href="https://www.youtube.com/watch?v=_FFpngCk52c">Gradle: State of the Build</a> presented by Luke Daley</li>
<li><a href="https://www.youtube.com/watch?v=4-f0vEWQ9Os">Hacking the Grails Spring Security 2.0 Plugin</a>, presented by Burt Beckwith</li>
<li><a href="https://www.youtube.com/watch?v=WT8S-dMLSVM">Ratpack: Under the Hood</a> presented by Luke Daley (<a href="https://github.com/ratpack/presentation-ratpack-under-the-hood-gr8conf-us-2015">slides</a>)</li>
<li>Slides on <a href="https://github.com/jondejong/GR8confUS-Grooscript">GrooScript</a> by Jon DeJong</li>
<li>Slides on <a href="http://www.slideshare.net/StevePember/gr8conf-us-2015-intro-to-event-sourcing-with-groovy">introduction to event sourcing</a> by Steve Pember</li>
<li>Slides on <a href="https://speakerdeck.com/johnrengelman/java-8-functional-programming-for-groovy-developers">Java 8 functional programming for Groovy developers</a> by John Engelman</li>
<li>Slides on <a href="http://www.slideshare.net/StevePember/gr8conf-us-2015-reactive-options-for-groovy">reactive options for Groovy</a> by Steve Pember</li>
<li>Slides on <a href="http://www.craigburke.com/angular-groovy-world/">Angular.JS in a Groovy world</a> by Craig Burke</li>
<li>Slides on <a href="http://www.slideshare.net/aestasit/infrastructure-automation-with-gradle-and-puppet-for-gr8conf-us-2015">infrastructure automation with Gradle and Puppet</a> by Andrey Adamovitch</li>
</ul>
</li>
<li><a href="http://www.infoq.com/presentations/eclipse-gradle">Eclipse &amp; Gradle, the best of both worlds</a>, at EclipseCon, by Hans Dockter and Etienne Studer</li>
<li>Dan Woods <a href="http://fr.slideshare.net/danveloper/ratpack-web-framework">Ratpack web framework</a> presentation at UberConf</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Keegan Witt morphed a <a href="https://twitter.com/keeganwitt/status/613718739217948672">3 hour running Bash script of HDFS commands into a 10 seconds Groovy script</a> using the Java APIs</li>
<li>The <a href="https://twitter.com/danveloper/status/626768428498579456">killer app for Groovy is Gradle</a>, says Ken Kousen</li>
<li>Erik Pragt spotted a <a href="https://twitter.com/epragt/status/631440665981923328">nice Groovy in Action, 2nd edition, review</a> by Andrew Binstock, on Oracle&rsquo;s Java Magazine</li>
<li><a href="https://twitter.com/ratpackweb/status/631781599621677056">Redis session storage coming to Ratpack</a> courtesy of Jeff Beck</li>
<li><a href="https://twitter.com/sdkmanager/status/630732523706949632">Gradle 2.6</a> available on GVM</li>
<li><a href="https://twitter.com/grooscript/status/630094999091343360">GrooScript&rsquo;s source code evolution</a> video</li>
<li>A video of a <a href="https://twitter.com/dhirajmahapatro/status/630030753758343168">Groovy ecosystem variant of the 2048</a> game</li>
<li>Iván López is working on a <a href="https://twitter.com/ilopmar/status/627801822347022336">Grails framework postgres-extensions plugin</a></li>
<li>Dan Woods learned that <a href="https://twitter.com/danveloper/status/627350450870820864">Bintray is using Ratpack</a></li>
<li>Marco Vermeulen is excited with the <a href="https://twitter.com/marc0der/status/627264041677901824">Gradle continuous build feature</a></li>
<li><a href="https://twitter.com/monzdrpower/status/627150274453487616">Another Groovy in Action 2nd edition picture</a>, from Epam Systems in Saint-Petersburg</li>
<li><a href="https://twitter.com/javazquez/status/627147102779367424">GVM is going to be renamed SDKman</a></li>
<li>Robert Fletcher discovered <a href="https://twitter.com/rfletcherEW/status/626889088738664449">Spock&rsquo;s @Use annotation</a> for using extension methods in tests</li>
<li>MrHaki shares a link to a <a href="http://asciidoctor.github.io/asciidoctor-gradle-examples/#_livereload_html_example">LiveReload integration with Gradle</a></li>
<li>Colin Harrington shares the ingredients of an <a href="https://twitter.com/ColinHarrington/status/626784519475965952">awesome sauce for Android development</a>: SwissKnife + Android Spock + Grooid tools</li>
<li><a href="https://twitter.com/sdkmanager/status/626321911056887808">Grails 3.0.4</a> available on GVM</li>
<li><a href="https://twitter.com/sdkmanager/status/626361104114053120">Grails 2.5.1</a> available on GVM</li>
<li>Burt Beckwith uploaded the <a href="https://twitter.com/burtbeckwith/status/632590650442129408">documentation for the Grails Spring Security Core plugin</a> v3.0.0.M1</li>
<li>Victor Mrtez believes Groovy rocks and is going to code another <a href="https://twitter.com/AnInfinite/status/626444888452808704">Jenkins CI plugin with Groovy and Gradle</a></li>
<li>At <a href="https://twitter.com/rfletcherEW/status/626599837421686784">GR8Conf US 2015, the Ratpack team gathered</a> to drink Sinatra&rsquo;s favorite drink!</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="https://twitter.com/glaforge/status/632669415876763648">Keegan Witt joined the Groovy team</a> as a committer</li>
<li>Do you want a <a href="http://gradle.org/gradle-com-sneak-peek/">Gradle.com sneak peek</a>?</li>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/102">Grails Diary</a> week 30</li>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/103">Grails Diary</a> week 31</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/104">Grails Diary</a> week 32</li>
<li>The 11th edition of the weekly <a href="http://groovycalamari.com/">Groovy Calamari</a></li>
<li>Marco Vermeulen has started a <a href="https://github.com/marcoVermeulen/groovy-scala-extension-module">Groovy extension module for Scala interop</a></li>
<li>The <a href="https://github.com/groovy/scriptom">Scriptom</a> project moved to the Groovy Github organization</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>A <a href="https://github.com/bertramdev/gSTOMP-client">Groovy STOMP client</a> by David Estes</li>
<li>Russell Hart&rsquo;s <a href="https://github.com/ratpack/hands-on-ratpack">Ratpack workshop code base</a></li>
<li>Kyle Boon <a href="https://twitter.com/kyleboon/status/626169879444504576">open sourced his Cellar HQ Ratpack application</a></li>
<li><a href="https://gist.github.com/danhyun/980319864f75967ccd8a">Utilize Gradle&rsquo;s idea plugin to set git as your default VCS</a> and to enable the Gradle tool from IntelliJ IDEA, by Danny Hyun</li>
<li>Václav Pech <a href="http://www.jroller.com/vaclav/entry/wagons_counted">counts wagons in Groovy</a> in this algorithmic teaser</li>
<li>Circle Stacker <a href="https://github.com/pieces029/circle-stacker">demo app using Groovy, Swiss Knife</a>, and built with Gradle</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/werner_cjd/status/632362950200819712">Free Gradle books</a> from O&rsquo;Reilly</li>
<li>Dierk König says that <a href="https://twitter.com/mittie/status/625320234778525696">Groovy in Action, 2nd edition, was #5 in Manning print book bestsellers</a> a month after appearing</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=ii693nUQwV0">Groovy Podcast episode 17</a>, recorded at GR8Conf US 2015</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="http://www.meetup.com/Austin-Groovy-and-Grails-Users/events/224611516/">Groovy 2 and Java 8, gotchas &amp; future</a>, at the Groovy user group in Austin on August 27th</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #74</title><link>https://glaforge.dev/posts/2015/07/21/groovy-weekly-74/</link><pubDate>Tue, 21 Jul 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/07/21/groovy-weekly-74/</guid><description>&lt;p>Although on a more irregular schedule, Groovy Weekly continues in the summer with its 74th edition, crossing the bar of the 3000 total news items tracked since it all started!&lt;/p>
&lt;p>Some interesting releases for this edition, like the &lt;a href="http://groovy-lang.org/changelogs/changelog-2.4.4.html">Groovy 2.4.4&lt;/a> release under the Apache umbrella! After a failed attempt, our second try was succesful, and it was particularly important to get it right out the door, as an &lt;a href="http://groovy-lang.org/security.html">important security issue&lt;/a> uncovered was fixed in this release, and all Groovy users (and frameworks and libraries using Groovy) should upgrade as soon as possible — especially as no older version will be released with the patch.&lt;/p></description><content:encoded>
<![CDATA[<p>Although on a more irregular schedule, Groovy Weekly continues in the summer with its 74th edition, crossing the bar of the 3000 total news items tracked since it all started!</p>
<p>Some interesting releases for this edition, like the <a href="http://groovy-lang.org/changelogs/changelog-2.4.4.html">Groovy 2.4.4</a> release under the Apache umbrella! After a failed attempt, our second try was succesful, and it was particularly important to get it right out the door, as an <a href="http://groovy-lang.org/security.html">important security issue</a> uncovered was fixed in this release, and all Groovy users (and frameworks and libraries using Groovy) should upgrade as soon as possible — especially as no older version will be released with the patch.</p>
<p>In the release section, last time, I had forgotten <a href="http://griffon-framework.org/releasenotes/griffon_2.3.0.html">Griffon 2.3.0</a> which was released on stage at GR8Conf Europe by Andrés Almiray. And in other releases, we also have <a href="https://twitter.com/grailsframework/status/619082386320572416">Grails 3.0.3</a> with important reloading improvements, <a href="https://groups.google.com/forum/#!topic/geb-user/2LXtg0pP4Do">Geb 0.12.0</a> which celebrated its 5th anniversary, and also <a href="https://github.com/CodeNarc/CodeNarc/blob/master/CHANGELOG.txt">Codenarc 0.24</a> that I had forgotten the previous edition as well.</p>
<p>It looks like Slack is really becoming very popular as both <a href="https://twitter.com/grailsframework/status/621780975870152704">Grails</a> and <a href="https://twitter.com/ratpackweb/status/621103100619681792">Ratpack</a> opened Slack channels!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://groovy-lang.org/changelogs/changelog-2.4.4.html">Groovy 2.4.4</a> released its first version under the Apache umbrella</li>
<li><a href="http://griffon-framework.org/releasenotes/griffon_2.3.0.html">Griffon 2.3.0</a> released</li>
<li><a href="https://twitter.com/grailsframework/status/619082386320572416">Grails 3.0.3</a> released with improvements to reloading</li>
<li><a href="https://groups.google.com/forum/#!topic/geb-user/2LXtg0pP4Do">Geb 0.12.0</a> released</li>
<li><a href="https://github.com/CodeNarc/CodeNarc/blob/master/CHANGELOG.txt">Codenarc 0.24</a> released</li>
<li><a href="https://twitter.com/CedricChampeau/status/623453951116070912">Deck2PDF 0.3</a> released by Cédric Champeau allowing HTML/JavaScript presentations to be saved as PDF or images</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>JaxEnter covers the <a href="https://jaxenter.com/groovy-2-4-4-has-landed-under-the-apache-foundation-119018.html">Groovy 2.4.4 release</a> under the Apache umbrella</li>
<li>Ryan Vanderwerf on <a href="http://rvanderwerf.blogspot.fr/2015/07/how-to-publish-grails-3-plugin.html">publishing Grails 3 plugin snapshots to your local Artifactory</a> server</li>
<li><a href="http://beckje01.com/blog/2015/07/12/grails-3-and-jacoco/">Grails 3 and JaCoCo</a> by Jeff Beck</li>
<li>Graeme Rocher announces the <a href="http://grails.io/post/124146177848/join-grails-community-on-slack">creation of a Grails Slack channel</a></li>
<li>First look at the new <a href="http://inthecheesefactory.com/blog/new-gradle-build-tools-with-gradle-2.5/en">Android Gradle build tools</a>: the new DSL structure and Gradle 2.5</li>
<li>Benjamin Muschko&rsquo;s Gradle in the trenches on <a href="https://gradle.org/tales-from-the-trenches-defining-versioning-strategies/">defining versioning strategies</a></li>
<li><a href="http://trickyandroid.com/gradle-tip-3-tasks-ordering/">Gradle task ordering tip</a> by Pavel Dudka</li>
<li><a href="https://rdmueller.github.io/Spock-Reports-with-Grails-3.0/">Spock-Reports plugin for Grails 3</a> by R.D. Müller</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Bogdan Danilyuk presented on <a href="https://vimeo.com/131395565">modularity with Grails</a> at GeekOut from real-life usage at TransferWise</li>
<li><a href="https://speakerdeck.com/bmuschko/advanced-dependency-management-with-gradle">Advanced dependency management with Gradle</a> by Benjamin Muschko</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Original Groovy co-founder <a href="https://twitter.com/jstrachan/status/622020452886663168">James Strachan states that Groovy is the language of Continuous Integration and Continuous Delivery</a>, and enjoys using it with Jenkins Flow and Docker workflows</li>
<li><a href="https://twitter.com/sdkmanager/status/621639983217119232">Groovy 2.4.4</a> available on GVM</li>
<li>Gradle 2.6 will be featuring a <a href="https://twitter.com/gradle/status/621458781273128960">test kit for functionally testing Gradle plugins</a></li>
<li>Andrés Almiray developed two <a href="https://twitter.com/aalmiray/status/621059717557821441">new Griffon AST transformations targeting JavaFX</a> with @ListChangeListener and @MapChangeListener</li>
<li><a href="https://twitter.com/GebFramework/status/619234577849028608">5th anniversary of Geb</a></li>
<li>Jennifer Strater has a <a href="https://twitter.com/JennStrater/status/620692583182790656">visualization of gender ratios in the Groovy community</a></li>
<li>Vladimír Oraný is doing heavy <a href="https://twitter.com/musketyr/status/621969659982647296">refactorings with confidence thanks to the Spock framework test coverage</a></li>
<li>A <a href="https://twitter.com/alvaro_sanchez/status/623477231050862592">Groovy Grape tour bus</a> in Australia spotted by Álvaro Sánchez!</li>
<li>Ryan Vanderwerf forked <a href="https://twitter.com/RyanVanderwerf/status/622978045490507780">grooid-templates to create a ready-made Glass or Android Wear app</a> template</li>
<li>Mario García made a new version of <a href="https://twitter.com/marioggar/status/621943669550149632">Grooid-templates with Spock framework support</a> for testing</li>
<li>A <a href="https://plugins.gradle.org/plugin/com.github.pedjak.dockerized-test/0.3.3">Gradle plugin for Dockerized tests</a></li>
<li><a href="https://twitter.com/sdkmanager/status/623355138699317248">GVM adds JBoss Forge</a> as new candidate</li>
<li><a href="https://twitter.com/sdkmanager/status/619076452978442240">Grails 3.0.3</a> available on GVM</li>
<li><a href="https://twitter.com/sdkmanager/status/619053543048278016">Vert.x 3.0</a> available on GVM</li>
<li><a href="https://twitter.com/sdkmanager/status/619050062354771968">Gradle 2.5</a> available on GVM</li>
<li>The <a href="https://twitter.com/djensen47/status/622090181861113856">Gradle dexinfo plugin</a> adds a new task to print out the dex method count of your Android projects without having to install separate tools</li>
<li>Danny Hyun reminds us about the <a href="https://twitter.com/Lspacewalker/status/621120598446575616">Ratpack Slack channel</a></li>
<li>You can <a href="https://twitter.com/ratpackweb/status/621103100619681792">sign-up for the Ratpack Slack channel</a></li>
<li>300 persons have joined the <a href="https://twitter.com/grailsframework/status/621780975870152704">Grails Slack channel</a></li>
<li>Graeme Rocher announces that <a href="https://twitter.com/graemerocher/status/620944698140704768">dynamic scaffolding will make a come back in the next Grails 3 release</a></li>
<li>Claus Ibsen <a href="https://twitter.com/davsclaus/status/621668001050415104">upgraded Apache Camel to Groovy 2.4.4</a> right after its release under the Apache flag</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Groovy 2.4.3 and below was affected by a <a href="http://groovy-lang.org/security.html">security issue</a> that has been fixed in Groovy 2.4.4. Projects should upgrade to 2.4.4 as soon as possible to avoid this security issue to be leveraged by malicious attackers.</li>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/101">Grails Diary</a> week 25 to 28</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/101">Grails Diary</a> week 29</li>
<li>Mockito is bringing some <a href="https://github.com/mockito/mockito/pull/266">better support for Groovy classes</a></li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Bertrand Goetzmann recorded a video of a <a href="https://twitter.com/bgoetzmann/status/623237871865040901">lunar lander game he developed with GroovyFX</a>, JavaFX and a bit of reactive programming</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Audio of <a href="https://twitter.com/groovypodcast/status/620708302142459904">Groovy podcast</a> episode 15</li>
<li><a href="https://plus.google.com/events/cs8io51q6nqdd0iafel0s0p0pjs">Groovy Podcast</a> episode 16</li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li><a href="http://mail-archives.apache.org/mod_mbox/incubator-groovy-users/201507.mbox/%3CCADQzvmk=3TwWzeGMN-HXwv4CUkbPBENusioviPf29_uJ=zLi0w@mail.gmail.com%3E">Groovy 2.4.4 release announcement</a> on the Groovy mailing-list by Cédric Champeau</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #73</title><link>https://glaforge.dev/posts/2015/07/08/groovy-weekly-73/</link><pubDate>Wed, 08 Jul 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/07/08/groovy-weekly-73/</guid><description>&lt;p>It’s definitely not Tuesday, but nonetheless, lots of accumulated great and Groovy content to share with you all! It’s not easy to keep up the pace, sorry for the irregular timing of this column — I might have to remove the “Weekly” part of the name! But you won’t regret this edition, packed with so much content!&lt;/p>
&lt;p>Already one month gone by since GR8Conf Europe 2015, but we have content to share about the event! And GR8Conf US 2015 is happening soon too! We also have presentations and videos from Gradle Summit and Greach 2015.&lt;/p></description><content:encoded>
<![CDATA[<p>It’s definitely not Tuesday, but nonetheless, lots of accumulated great and Groovy content to share with you all! It’s not easy to keep up the pace, sorry for the irregular timing of this column — I might have to remove the “Weekly” part of the name! But you won’t regret this edition, packed with so much content!</p>
<p>Already one month gone by since GR8Conf Europe 2015, but we have content to share about the event! And GR8Conf US 2015 is happening soon too! We also have presentations and videos from Gradle Summit and Greach 2015.</p>
<p>Guillaume Laforge tweeted about the recent download numbers of the Groovy project: for the first 6 monts of the year, Groovy was downloaded 4.5 million times (1 million through Bintray and 3.5 millions through Maven Central). In 6 months, Groovy was downloaded as much as the full year of 2014!</p>
<p>Another big milestone is the release in print of the second edition of Groovy in Action! Lots of fans tweeted their “ReGinA selfies!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://ratpack.io/versions/0.9.18">Ratpack 0.9.18</a> released</li>
<li><a href="https://groups.google.com/forum/?fromgroups=#!topic/vertx/xgGgQcDeX04">Vert.x 3.0</a> released</li>
<li><a href="https://discuss.gradle.org/t/gradle-2-5-released/10490">Gradle 2.5</a> released with continuous build and dependency substitution rules</li>
<li><a href="https://twitter.com/grailsframework/status/610790605980045312">Grails 3.0.2</a> released with lots of improvements, and the first release under OCI&rsquo;s leadership</li>
<li><a href="https://twitter.com/grooscript/status/617354776897056768">GrooScript 1.1.2</a> released</li>
<li><a href="https://twitter.com/grooscript/status/614796850395283456">GrooScript Grails 3 plugin</a> released in version 1.0.0</li>
<li><a href="https://twitter.com/sdkmanager/status/611213808569335808">GVM 2.4.2</a> released with key security patches</li>
<li><a href="https://twitter.com/ysb33r/status/613705160238370818">Groovy VFS 1.0-beta-3</a> released</li>
<li><a href="https://twitter.com/ysb33r/status/613711977240657927">Groovy VFS command-line 1.0-beta-3</a> released</li>
<li><a href="https://marketplace.eclipse.org/content/buildship-gradle-integration">Buildship Gradle plugin support</a> released</li>
<li><a href="http://glu.977617.n3.nabble.com/ANN-glu-5-6-1-released-td4026928.html">Glu 5.6.1</a> released</li>
<li><a href="http://spring.io/blog/2015/07/02/spring-boot-1-2-5-released">Spring Boot 1.2.5</a> released</li>
<li><a href="http://gradle.org/release-candidate/">Gradle 2.5-rc-2</a> released</li>
<li><a href="https://twitter.com/grainframework/status/613008570691420160">Grain 0.6.4</a> released with updated Asciidoctor support for include macros</li>
<li><a href="https://twitter.com/SonarSource/status/610725375400935424">SonarQube Gradle 1.0</a> makes it easier to cover more versions of both Gradle and Sonar</li>
<li><a href="https://twitter.com/pickypg/status/610534606425735168">ElasticSearch Groovy client 1.6.0</a> released with improved closure conversion</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Graeme Rocher sums up his <a href="http://grails.io/post/120598713483/grails-at-oci-week-1">first week at his new job at OCI</a></li>
<li><a href="http://www.aurorasolutions.io/blog/grails-spring-security-filter-2fa-yubico-example/">Grails Spring Security filter for professional 2 factor authentication</a> with Yubico</li>
<li>Pavel Dudka shares a tip on <a href="http://trickyandroid.com/gradle-tip-2-understanding-syntax/">understanding the Groovy syntax for working with Gradle</a></li>
<li><a href="http://www.tothenew.com/blog/day-1-gr8conf-eu-2015/">Day one coverage of GR8Conf Europe</a> 2015 by To The New Digital</li>
<li><a href="http://www.tothenew.com/blog/day-2-gr8conf-eu-2015/">Day two coverage of GR8Conf Europe</a> 2015 by To The New Digital</li>
<li><a href="http://nemerosa.ghost.io/2015/07/01/publishing-to-the-maven-central-using-gradle/">Publishing to Maven Central using Gradle</a> by Damien Coraboeuf</li>
<li>Petri Kainulainen helps you <a href="http://www.petrikainulainen.net/getting-started-with-gradle/">getting started with Gradle</a></li>
<li><a href="https://twitter.com/gradleplugins/status/613070984782999553">Sync content on Amazon S3 with Gradle</a></li>
<li>Ryan Harter on <a href="http://ryanharter.com/blog/2015/06/18/hosting-a-private-maven-repo-on-amazon-s3/">hosting a private Maven repository on Amazon S3</a></li>
<li>Anto Aravinth published a <a href="https://twitter.com/antoaravinth/status/612112565137571840">GrooScript plugin for Karma</a></li>
<li>Running <a href="http://www.mkyong.com/spring-mvc/gradle-spring-4-mvc-hello-world-example-annotation/">Spring MVC 4 with Gradle and Gretty</a></li>
<li><a href="https://objectpartners.com/2015/06/10/websockets-in-grails-3-0/">WebSockets in Grails 3.0</a> by Mike Plummer</li>
<li><a href="https://blog.nraboy.com/2015/04/using-gradle-in-your-command-line-android-project/">Using Gradle in your command-line Android projects</a> by Nic Raboy</li>
<li>Iván López about <a href="http://www.kaleidos.net/blog/1168/speaking-at-gr8conf/">speaking at GR8Conf</a> Europe 2015</li>
<li>Painless Android development with Groovy and SwissKnife
<ul>
<li><a href="http://www.codebulb.ch/2015/06/painless-android-development-with-groovy-and-swissknife-part-1.html">part one</a></li>
<li><a href="http://www.codebulb.ch/2015/06/painless-android-development-with-groovy-and-swissknife-part-2.html">part two</a></li>
<li><a href="http://www.codebulb.ch/2015/06/painless-android-development-with-groovy-and-swissknife-part-3.html">part three</a></li>
</ul>
</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>GR8Conf Europe 2015
<ul>
<li>The <a href="https://twitter.com/gr8conf/status/605991341676494848">GR8Conf keynote</a> by Guillaume Laforge was livestreamed on YouTube</li>
<li>The slides of the <a href="https://speakerdeck.com/glaforge/groovy-state-of-the-union-gr8conf-europe-2015">Groovy keynote</a> by Guillaume Laforge</li>
<li><a href="https://speakerdeck.com/glaforge/groovy-with-style-gr8conf-europe-2015">Groovy with style</a> by Guillaume Laforge</li>
<li><a href="http://danhyun.github.io/gr8conf-eu-2015-contribute-to-gr8tech/#/">Contributing to GR8 technologies</a> by Danny Hyun</li>
<li><a href="http://danhyun.github.io/gr8conf-eu-2015-groovy-safari/#/">Groovy Safari</a> by Danny Hyun, a beginner workshop</li>
<li><a href="http://fr.slideshare.net/alvarosanchezmariscal/ratpack-101-gr8conf-2015">Ratpack 101 workshop</a> by Álvaro Sánchez Mariscal</li>
<li><a href="http://jlstrater.github.io/No-Nonsense-NoSQL/#/">No-nonsense NoSQL</a> by Jennifer Strater</li>
<li><a href="https://speakerdeck.com/bsideup/gr8conf-groovy-under-macroscope">Groovy under Macro-scope</a> by Sergei Egorov</li>
<li><a href="http://fr.slideshare.net/ilopmar/gr8conf-2015-spring-boot-and-groovy-what-more-do-you-need">Spring Boot and Groovy</a> by Iván López</li>
<li>Göran Ehrsson on <a href="http://gr8crm.github.io/">customer relationship management plugins for Grails</a></li>
<li><a href="https://speakerdeck.com/glaforge/groovy-rest-gr8conf-europe-2015">Groovy REST</a> by Guillaume Laforge</li>
<li><a href="http://fr.slideshare.net/ryanvanderwerf/all-your-legos-are-belong-to-us-testout">Groovy and legos</a> by Ryan Wanderverf</li>
<li><a href="http://imada.sdu.dk/~jamik/gr8conf/geb.html#/">Geb for testing Grails applications</a> by Jacob Aae Mikkelsen</li>
<li><a href="http://imada.sdu.dk/~jamik/gr8conf/lessons-learned.html#/">Lessons learned teaching a Groovy / Grails course</a> by Jacob Aae Mikkelsen</li>
<li><a href="http://fr.slideshare.net/ilopmar/gr8conf-2015-testing-with-spock-the-logical-choice">Testing with Spock, the logical choice</a>, by Iván López</li>
<li><a href="http://fr.slideshare.net/sascha_klein/groovy-ontheshell-gr8-48989676">Groovy on the Shell</a> by Alexander Klein</li>
<li><a href="http://fr.slideshare.net/sascha_klein/android-on-groovygr8-48989809">Android on Groovy</a> by Alexander Klein</li>
<li><a href="http://fr.slideshare.net/alvarosanchezmariscal/stateless-authentication-for-microservices-gr8conf-2015">Stateless authentication for microservices</a> by Álvaro Sánchez Mariscal</li>
<li><a href="http://es.slideshare.net/JorgeFrancoLeza/grooscript-gr8conf-2015">GrooScript in Action</a> by Jorge Franco Leza</li>
<li><a href="http://fr.slideshare.net/aestasit/rapid-web-application-development-with-groovy-ratpack-for">Rapid application development with Ratpack</a> by Andrey Adamovich</li>
<li>Guillaume Laforge presented on <a href="https://glaforge.dev/talks/2015/06/24/groovy-on-android-for-the-paris-android-user-group/">Groovy on Android</a> at the Paris Android User Group</li>
<li><a href="http://zeroturnaround.com/rebellabs/gradle-hot-or-not-by-andres-almiray/">Gradle hot or not</a>, by Andrés Almiray</li>
<li>Iván López&rsquo; slides and code on <a href="https://twitter.com/ilopmar/status/618681788307800064">metaprogramming options with Groovy</a></li>
</ul>
</li>
<li>Greach 2015 videos
<ul>
<li>Andrés Almiray presented the <a href="https://www.youtube.com/watch?v=gmDi-8k2NhQ">Groovy Ecosystem</a></li>
<li><a href="http://greachconf.com/speakers/steve-pember-groovy-options-for-reactive-programming/">Groovy options for reactive programming</a> by Steve Pember</li>
<li><a href="http://greachconf.com/speakers/russel-winder-gpars-remoting/">GPars remoting</a> by Russel Winder</li>
<li><a href="http://greachconf.com/speakers/rene-groeschke-beyond-gradle-2-0/">Beyond Gradle 2</a> by René Gröschke</li>
<li><a href="http://greachconf.com/speakers/schalk-w-cronje-idiomatic-gradle-plugin-writing/">Idiomatic Gradle plugin writing</a> by Schalk Cronjé</li>
<li><a href="http://greachconf.com/speakers/steve-pember-advanced-microservice-concerns/">Advanced micro services concerns</a> by Steve Pember at Greach 2015</li>
</ul>
</li>
<li>Gradle Summit presentations
<ul>
<li><a href="https://speakerdeck.com/bmuschko/state-of-the-art-gradle-multi-module-builds">State of the art Gradle multi-module builds</a> by Benjamin Muschko at Gradle Summit</li>
<li><a href="https://speakerdeck.com/bmuschko/advanced-dependency-management-with-gradle">Advanced dependency management with Gradle</a> by Benjamin Muschko at Gradle Summit</li>
<li><a href="http://fr.slideshare.net/jmcgarr/centralized-team-in-a-decentralized-world-engineering-tools-at-netflix">A centralized team in a decentralized world</a> Mike McGarr at Gradle Summit</li>
</ul>
</li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Andrés Almiray interviewed at Greach about the <a href="https://www.youtube.com/watch?v=OtzQYJxHcaA">Griffon ecosystem</a> (in Spanish but subtitled in English)</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Guillaume Laforge announces that <a href="https://twitter.com/glaforge/status/617061188199936000">Groovy has been downloaded 4.5 million times for the first half of the year</a>, as much as the whole of 2014. With 1M downloads from Bintray and 4.5M downloads from Maven Central.</li>
<li>Guillaume Laforge updated the <a href="https://twitter.com/glaforge/status/617443128988028928">Groovy Web Console to the latest Groovy 2.4.3</a>, developed with the latest Gaelyk 2.1.2 update</li>
<li>Graeme Rocher <a href="https://twitter.com/graemerocher/status/616903346499272704">reworked the reloading in Grails 3.0.3</a></li>
<li>Søren Berg Glasius and Guillaume Laforge are launched <a href="https://twitter.com/glaforge/status/605992876753985536">the 7th edition of GR8Conf Europe 2015</a>!</li>
<li>Ixchel Ruiz says <a href="https://twitter.com/ixchelruiz/status/606002309349416960">Groovy is alive and thriving</a>!</li>
<li>Schalk Cronjé drew his <a href="https://twitter.com/ysb33r/status/606009047607865344">summary of Guillaume Laforge&rsquo;s GR8Conf keynote</a></li>
<li>Kostis Kapelonis feels that <a href="https://twitter.com/codepipes/status/617355655352057858">even after the release of Java 8, Groovy closures are still more convenient</a></li>
<li>Seems like <a href="https://twitter.com/DexafreeHTC/status/616640764173512704">developing on Android with Groovy and RxJava can blow your mind</a>!</li>
<li>Dierk König shares interesting <a href="https://twitter.com/mittie/status/616613035126484992">ranking information about Groovy in Action on Amazon</a></li>
<li>ReGinA selfies:
<ul>
<li><a href="https://twitter.com/sebi2706/status/617003864068386816">Sébastien Blanc</a></li>
<li><a href="https://twitter.com/fifthposition/status/615593893082603521">Ben Klein</a></li>
<li><a href="https://twitter.com/rfletcherEW/status/611990264698114048">Robert Fletcher</a></li>
<li><a href="https://twitter.com/glaforge/status/611072178407219201">Guillaume Laforge</a></li>
</ul>
</li>
<li>Russell Hart says that <a href="https://twitter.com/rus_hart/status/616586423102218240">GroovyChainAction is back in Ratpack 0.9.18</a></li>
<li>Russell Hart encourages users to <a href="https://twitter.com/rus_hart/status/616584233059000320">seek for help on the Ratpack forum</a></li>
<li><a href="https://twitter.com/sdkmanager/status/616505672537083905">Spring Boot 1.2.5</a> available on GVM</li>
<li>If you&rsquo;re <a href="https://twitter.com/RebelLabs/status/615963195006656513">missing Maven archetypes in Gradle, try Lazybones</a>, says Andrés Almiray</li>
<li>The <a href="https://twitter.com/GebFramework/status/615487750037483520">Geb framework team has caught up with the outstanding pull requests</a>, time to submit some more!</li>
<li>Andrés Almiray claims that <a href="https://twitter.com/aalmiray/status/615275454497452032">migration from Maven to Gradle is a matter of &ldquo;when&rdquo;, not of &ldquo;if&rdquo;</a></li>
<li>Tim Yates loves how <a href="https://twitter.com/tim_yates/status/615261252328292352">Spock makes mocking and interaction testing easy and readable</a> (with a code sample)</li>
<li>The <a href="https://twitter.com/GebFramework/status/614407603620327424">book of Geb has migrated to Asciidoctor with executable code samples</a></li>
<li>Danny Hyun encourages folks to use the <a href="https://twitter.com/Lspacewalker/status/614148984832139265">asset pipeline Gradle plugin</a></li>
<li>Peter Ledbrook points at the <a href="https://twitter.com/pledbrook/status/613998420404125696">Gradle continuous build feature that responds to filesystem changes</a> by running tests again</li>
<li><a href="https://twitter.com/sdkmanager/status/613992559900295168">Gradle 2.5-rc-1</a> available on GVM</li>
<li>Kevin Tan pointed me at <a href="https://twitter.com/S1lv3rd3m0n/status/613937882605027328">another Groovy-built Android application available in the Play store</a></li>
<li>Ratpack.io is now powered by the <a href="https://twitter.com/Lspacewalker/status/613101435270643716">Ratpack asset pipeline plugin</a></li>
<li><a href="https://twitter.com/sdkmanager/status/612555268677836800">Legacy versions of Groovy are restored on GVM</a>, thanks to the Bintray team, Marco Vermeulen and Guillaume Laforge</li>
<li><a href="https://twitter.com/danveloper/status/611604849508151296">AngularJS Annotate is a Gradle Asset Pipeline module</a> that allows AngularJS code to be minified</li>
<li>Cédric Champeau announces <a href="https://twitter.com/CedricChampeau/status/611332345707139074">older versions of Groovy to be available on Bintray</a></li>
<li>Say hello to <a href="https://twitter.com/davydotcom/status/611212285382664193">automatic relative asset url replacement in static HTML</a> with the Gradle asset pipeline plugin</li>
<li><a href="https://twitter.com/danveloper/status/611163598774235136">Location of ratpack.groovy is now configurable</a> via the Ratpack Gradle plugin says Dan Woods</li>
<li>An initial list of <a href="https://twitter.com/grailsframework/status/611107170692825088">Grails 3 plugins to be updated in priority</a></li>
<li><a href="https://twitter.com/sdkmanager/status/610783733642317824">Grails 3.0.2</a> available on GVM</li>
<li>Jorge Martín says <a href="https://twitter.com/arasthel92/status/610209132306694144">what&rsquo;s a hipster Android project looks like in Groovy</a></li>
<li>All of Ken Kousen&rsquo;s <a href="https://twitter.com/kenkousen/status/610077557065691136">Groovy videos for O&rsquo;Reilly are online</a></li>
<li>Graeme Rocher <a href="https://twitter.com/olavgg/status/608650670279946241">improved Grails 3 performance by 20%</a></li>
<li><a href="https://twitter.com/eugenekamenev/status/608124188868669441">OrientDB Groovy supports the Graph API</a>, Gremlin, and nice IntelliJ IDEA support says Eugene Kamenev</li>
<li>You can find <a href="https://twitter.com/jmiguel/status/606745439468187648">GORM in Copenhagen</a></li>
<li>Original <a href="https://twitter.com/jstrachan/status/606831442425999360">Groovy project founder is hacking Groovy again</a> within IntelliJ IDEA, in the shell and in Jenkins via the Job DSL for Continuous Integration and Continuous Delivery, and he&rsquo;s forgotten how much Groovy coding is fun!!</li>
<li>Craig Burke is investigating <a href="https://twitter.com/craigburke1/status/607973363105923072">using JFairy for sample value generation with GORM</a></li>
<li>Small <a href="https://twitter.com/ratpackweb/status/608063775825096706">Ratpack enhancement with sessions and Java 8</a> default interface methods</li>
<li><a href="https://twitter.com/Lspacewalker/status/606561199505801216">Danny Hyun is a happy Groovy contributor</a>!</li>
<li>Philipp Berner released a <a href="https://github.com/KeepSafe/dexcount-gradle-plugin">Gradle plugin that reports the number of method references in Android APKs</a></li>
<li><a href="https://twitter.com/aalmiray/status/606435403420299264">Lots of Gradle goodies to be found in the Griffon build</a>, says Andrés Almiray</li>
<li>Guillaume Laforge showing a <a href="https://twitter.com/Lspacewalker/status/606421415512948736">REST example with Ratpack using the byContent method</a></li>
<li><a href="https://twitter.com/RyanVanderwerf/status/606382108156657665">Groovy 2.4.3 is 2x faster than 2.2 at starting on the Lego EV3 robot</a> says Ryan Vanderwerf</li>
<li><a href="https://twitter.com/gr8conf/status/606126974940147712">Andrés Almiray is releasing Griffon 2.3 on stage with style</a> at GR8Conf Europe 2015!</li>
<li><a href="https://twitter.com/aalmiray/status/606022186135109632">Andrés Almiray pushed his first commit as a Groovy committer</a> at Apache</li>
<li><a href="https://twitter.com/sdkmanager/status/605875255974838273">JBake 2.4</a> available on GVM</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="https://groups.google.com/forum/?hl=fr#!forum/paris-groovy-user-group">Paris Groovy user group is going to reboot</a> and come back in September, but starts with a fresh mailing-list on Google Groups. So if you&rsquo;re in the Paris area, join the band!</li>
<li><a href="https://twitter.com/ManningBooks/status/610553583940014080">Groovy in Action 2nd edition is available in print and eBook formats</a></li>
<li><a href="https://github.com/aaronzirbes/kinesis-http-adapter">Ratpack AWS Kinesis consumer</a> that forwards to an HTTP endpoint by Aaron Zirbes</li>
<li>Mario García releases <a href="http://mariogarcia.github.io/fnz/">FlambdaZ, a new Groovy functional library</a></li>
<li>Udacity offers a <a href="http://gradle.org/udacitys-gradle-for-android-training/">free online training on Gradle for Android</a> app development</li>
<li>A new <a href="https://twitter.com/infiniteskills/status/614547512419074048">practical Groovy programming video</a> by Ken Kousen on O&rsquo;Reilly</li>
<li><a href="https://twitter.com/theaviary/status/611622405581197312">New location for the Griffon mailing-lists</a></li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="https://twitter.com/JacobAae/status/610569967864532993">Grails Diary</a> week 24</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/98">Grails Diary</a> for weeks 22 and 23</li>
<li>The new <a href="https://twitter.com/glaforge/status/609462312840335362">Apache hosted mailing-lists are now archived on Nabble</a> like the old ones</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://twitter.com/groovypodcast/status/611944311551340545">Groovy podcast episode 14</a></li>
<li><a href="https://twitter.com/groovypodcast/status/616287886531522560">Groovy Podcast episode 15</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>The <a href="https://twitter.com/mittie/status/606845647459917825">source code of Groovy in Action (2nd ed) is available on Github</a></li>
<li>Dan Woods tweeted a <a href="https://twitter.com/danveloper/status/608298173208100864">hello world Ratpack in less than 140 characters</a></li>
<li>A sample <a href="https://gist.github.com/chiquitinxx/b661f27c647c3cfe5d38">REST API interaction from the client side with Groovy trait, JQuery and GrooScript</a></li>
<li>A <a href="https://twitter.com/timfox/status/608304503939584003">Vert.x app in Groovy in a tweet</a> by Tim Fox</li>
<li>Based on David Gageot&rsquo;s http library, Guillaume Laforge shows a little snippet for <a href="https://gist.github.com/glaforge/b5c55651f523b3281fc9">another hello world web server in a line of Groovy</a> code</li>
<li>Russ Hart has put online his <a href="https://github.com/rhart/hands-on-ratpack">Ratpack hands-on</a></li>
<li>A trick to <a href="https://dineshramitc.wordpress.com/2015/07/07/speed-up-gradle-builds-in-android-studio/">speedup Gradle builds in Android Studio</a></li>
<li>A <a href="http://forum.ratpack.io/Ratpack-with-React-hot-loader-support-td1141.html">Ratpack with React</a> hot loader support</li>
<li><a href="https://twitter.com/marc0der/status/612554222920404992">Marco Vermeulen loves Groovy&rsquo;s productivity</a> and shows a code snippet to demonstrate it</li>
<li>Eugene Kamenev published a <a href="https://twitter.com/eugenekamenev/status/613038251155152896">sample app mixing Groovy, Spring Boot, OrientDB</a> and Gremlin</li>
<li>Write your <a href="https://twitter.com/grooscript/status/609093126041550848">ReactiveX code in Groovy with GrooScript</a> and run in a Javascript environment</li>
<li>Arnaud Esteve shows a draft of a <a href="https://twitter.com/ArnaudEsteve/status/608926350028759041">Groovy DSL for Vert.x 3&rsquo;s router</a></li>
<li>Tim Fox&rsquo; tweetable <a href="https://twitter.com/timfox/status/608304503939584003">hello world Vert.x app in Groovy</a></li>
<li>Dan Woods&rsquo; tweetable <a href="https://twitter.com/danveloper/status/608298173208100864">Ratpack hello world app in Groovy</a></li>
<li>A <a href="https://twitter.com/grooscript/status/607613663978078208">GrooScript DSL to use the Google Maps API</a></li>
<li><a href="https://twitter.com/grooscript/status/607678436379885568">Rest API from the client-side with GrooScript</a></li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>Discussing an <a href="http://www.groovy-lang.org/mailing-lists.html#nabble-td5725804">experimental @POJO AST transformation</a> by Paul King for a Groovy-less runtime</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li><a href="https://twitter.com/gradle/status/615986939905351680">Netflix is hiring Gradle talents</a></li>
<li><a href="http://www.indeed.com/jobanalytics/jobtrends?q=gradle">Gradle job trend on the rise</a></li>
<li>A <a href="https://twitter.com/deigote/status/608695031046795265">Groovy and Grails job in Munich</a>, Germany</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Dierk König announces that <a href="https://twitter.com/mittie/status/614872367106555904">Groovy in Action is back in stock at Amazon</a> since the beginning of the month</li>
<li><a href="https://twitter.com/mittie/status/610779780246577152">Groovy in Action 2nd edition was #1 on Amazon&rsquo;s hot new releases</a> says Dierk König</li>
<li><a href="http://www.groovy-lang.org/mailing-lists.html#nabble-td5725795">Groovy 2 tutorial cookbook</a> update by Duncan Dickinson available on LeanPub</li>
<li><a href="https://twitter.com/gradle/status/614527377440407552">Korean and Japanese books on Gradle</a> available</li>
<li>A new Manning MEAP upda<a href="http://manning.com/kapelonis/">te to the Java testing with Spock book</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy on Android for the Paris Android User Group</title><link>https://glaforge.dev/talks/2015/06/24/groovy-on-android-for-the-paris-android-user-group/</link><pubDate>Wed, 24 Jun 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2015/06/24/groovy-on-android-for-the-paris-android-user-group/</guid><description>&lt;p>Yesterday, I had the pleasure to speak about Groovy on Android at the &lt;a href="http://www.meetup.com/fr/Android-Paris/events/223305279/">Paris Android User Group&lt;/a>. This is an evolution of my presentation from the DroidCon 2014 conference, with a few updates, in particular more coverage of SwissKnife.&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="6284de7ee6d24035955a19bf6173a7a5" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script></description><content:encoded>
<![CDATA[<p>Yesterday, I had the pleasure to speak about Groovy on Android at the <a href="http://www.meetup.com/fr/Android-Paris/events/223305279/">Paris Android User Group</a>. This is an evolution of my presentation from the DroidCon 2014 conference, with a few updates, in particular more coverage of SwissKnife.</p>
<script async class="speakerdeck-embed" data-id="6284de7ee6d24035955a19bf6173a7a5" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy in Action, 2nd edition is not a myth!</title><link>https://glaforge.dev/posts/2015/06/17/groovy-in-action-2nd-edition-is-not-a-myth/</link><pubDate>Wed, 17 Jun 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/06/17/groovy-in-action-2nd-edition-is-not-a-myth/</guid><description>&lt;p>It took us a while to get it finished, but I assure you it was well worth the wait, and &lt;a href="http://www.manning.com/koenig2/">Groovy in Action, 2nd edition&lt;/a>, is real! I just received my copies this morning!&lt;/p>
&lt;p>Congrats to my fellow co-authors, and in particular to Dierk and Paul for their patience and dedication to this project, without whom the book would have never gone through its print stage!&lt;/p>
&lt;p>&lt;figure>
&lt;a href="#img-c7438e8c451f831b08c5f145616d009f">
&lt;img src="https://glaforge.dev/img/misc/2015-06-17&amp;#43;09.22.37.jpg"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-c7438e8c451f831b08c5f145616d009f">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/2015-06-17&amp;#43;09.22.37.jpg"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p></description><content:encoded>
<![CDATA[<p>It took us a while to get it finished, but I assure you it was well worth the wait, and <a href="http://www.manning.com/koenig2/">Groovy in Action, 2nd edition</a>, is real! I just received my copies this morning!</p>
<p>Congrats to my fellow co-authors, and in particular to Dierk and Paul for their patience and dedication to this project, without whom the book would have never gone through its print stage!</p>
<p><figure>
  <a href="#img-c7438e8c451f831b08c5f145616d009f">
    <img src="/img/misc/2015-06-17&#43;09.22.37.jpg"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-c7438e8c451f831b08c5f145616d009f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/2015-06-17&#43;09.22.37.jpg"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>My GR8Conf presentations slides</title><link>https://glaforge.dev/talks/2015/06/04/my-gr8conf-presentations-slides/</link><pubDate>Thu, 04 Jun 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2015/06/04/my-gr8conf-presentations-slides/</guid><description>&lt;p>It&amp;rsquo;s been a busy and intense week here at Copenhagen for &lt;a href="http://gr8conf.eu/#/">GR8Conf Europe 2015&lt;/a>! Great interactions and discussions with the Groovy community, and flawless organization.&lt;/p>
&lt;p>I had the chance to present three talks this year, and I&amp;rsquo;m embedding my slides below. First of all, I&amp;rsquo;ve given the usual Groovy keynote, with a Groovy state of the union.&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="a961bdc6faeb466199eded25f2a402a3" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;p>Later on, I gave a talk on Groovy style! With interesting tips&amp;rsquo;n tricks, programming style advice.&lt;/p></description><content:encoded>
<![CDATA[<p>It&rsquo;s been a busy and intense week here at Copenhagen for <a href="http://gr8conf.eu/#/">GR8Conf Europe 2015</a>! Great interactions and discussions with the Groovy community, and flawless organization.</p>
<p>I had the chance to present three talks this year, and I&rsquo;m embedding my slides below. First of all, I&rsquo;ve given the usual Groovy keynote, with a Groovy state of the union.</p>
<script async class="speakerdeck-embed" data-id="a961bdc6faeb466199eded25f2a402a3" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>Later on, I gave a talk on Groovy style! With interesting tips&rsquo;n tricks, programming style advice.</p>
<script async class="speakerdeck-embed" data-id="5e39a3f0f8ae4cc58803bc70c903bd56" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>Lastly, with my Web API hat on, I talked about various tools for creating and testing Web APIs.</p>
<script async class="speakerdeck-embed" data-id="7ae90f9be4c54a15a2d6c723fbca623a" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #72</title><link>https://glaforge.dev/posts/2015/06/02/groovy-weekly-72/</link><pubDate>Tue, 02 Jun 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/06/02/groovy-weekly-72/</guid><description>&lt;p>This 72th edition is being authored live from the Hackergarten session at &lt;a href="http://gr8conf.eu/#/">GR8Conf Europe 2015&lt;/a>, in Copenhagen (Denmark), where Groovy hackers are working on various contributions to the Groovy and Grails ecosystem, updating Grails 2 plugins to Grails 3, crafting a Groovy UI builder for Android, or work on some Gradle plugins. A busy evening!&lt;/p>
&lt;p>It’s been a little long while coming, but &lt;a href="https://twitter.com/mittie/status/604634440065761280">Groovy in Action 2nd edition has just been sent to the printers&lt;/a>! And the ebook formats will also be available this week!&lt;/p></description><content:encoded>
<![CDATA[<p>This 72th edition is being authored live from the Hackergarten session at <a href="http://gr8conf.eu/#/">GR8Conf Europe 2015</a>, in Copenhagen (Denmark), where Groovy hackers are working on various contributions to the Groovy and Grails ecosystem, updating Grails 2 plugins to Grails 3, crafting a Groovy UI builder for Android, or work on some Gradle plugins. A busy evening!</p>
<p>It’s been a little long while coming, but <a href="https://twitter.com/mittie/status/604634440065761280">Groovy in Action 2nd edition has just been sent to the printers</a>! And the ebook formats will also be available this week!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://ratpack.io/versions/0.9.17">Ratpack 0.9.17</a> released with new session support</li>
<li><a href="https://twitter.com/arasthel92/status/603990688481615875">SwissKnife 1.3.1</a> released with Android Res injection and better Parcelable support</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Immutable <a href="https://boxfuse.com/blog/gradle-plugin.html">infrastructure made easy with a Gradle</a> plugin</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="https://www.parleys.com/tutorial/groovy-light-java-8-1">Groovy, in the light of Java 8</a>, presented by Guillaume Laforge at Devoxx Belgium (registration required, but otherwise free content)</li>
<li><a href="http://greachconf.com/speakers/markus-schlichting-documentation-brought-to-life-asciidoctor-gradle/">Documentation brought to life with Asciidoctor and Gradle</a>, presented by Markus Schlichting at Greach</li>
<li>Marco Vermeulen on the <a href="http://greachconf.com/speakers/marco-vermeulen-groovy-environment-manager-2015/">Groovy enVironment Manager</a>, 2015 edition, at Greach</li>
<li><a href="http://greachconf.com/speakers/ivan-lopez-ast-groovy-transformers-more-than-meets-the-eye/">Groovy Transformers</a>: more than meets the eye by Ivan Lopez at Greach</li>
<li><a href="http://greachconf.com/speakers/jochen-theodorou-groovy-past-and-future/">Groovy past and future</a> by Jochen Theodorou at Greach</li>
<li>Claus Ibsen shares a video of <a href="https://twitter.com/davsclaus/status/603480717390172160">Groovy, Grails and Apache Camel</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/mittie/status/604634440065761280">Groovy in Action 2nd edition has been sent to the printers</a>! eBook versions available this week!</li>
<li>Object Computing Inc announces <a href="https://twitter.com/ObjectComputing/status/605496734265925632">Dave Klein and Collin Harrington are joining the Grails team</a></li>
<li>Schalk Cronjé learned about <a href="https://twitter.com/ysb33r/status/605693295164051456">Shoogr</a> at GR8Conf Europe and is thinking about integration with Groovy VFS</li>
<li>Dan Woods believes <a href="https://twitter.com/danveloper/status/605602603016310784">Ratpack and Spring Cloud is as natural a marriage as cloud native gets</a></li>
<li>Add <a href="https://twitter.com/ratpackweb/status/604446744198266881">LiveReload support to your Ratpack</a> app development</li>
<li>CardShifter&rsquo;s been a happy user of <a href="https://twitter.com/Cardshifter/status/604423648640925696">Groovy for its game design</a> work</li>
<li>Yann Le Moigne is looking for <a href="https://twitter.com/LeMoigneY/status/604310642737074177">feedback for two of his Ratpack modules</a></li>
<li><a href="https://twitter.com/sdkmanager/status/604002646530342912">GVM mentions some alternative names</a> getting traction</li>
<li>Jorge Martín says <a href="https://twitter.com/arasthel92/status/603843044950020096">you can&rsquo;t truly understand the power of Groovy AST transformations until you realize how limited Java APT is</a></li>
<li>Dierk König tells us that <a href="https://twitter.com/mittie/status/603286822396010496">Groovy in Action 2nd edition will be 912 pages</a> long</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/605058483604570112">Create a REST API fake with Groovy</a>, using npm faker and JSON server</li>
<li>Eugene Kamenev shares the code of a <a href="https://twitter.com/eugenekamenev/status/604248101927391232">Spring Boot and OrientDB app in Groovy</a></li>
<li>Eugene Kamenev is working on <a href="https://github.com/eugene-kamenev/orientdb-groovy">Groovy wrappers around OrientDB</a>&rsquo;s APIs</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/mittie/status/605349387892604928">Groovy in Action 2nd edition comes with 10 thousand lines of code</a> tested automatically, notes Dierk König</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://twitter.com/groovypodcast/status/603575371255033856">Groovy Podcast episode 13</a> published</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #71</title><link>https://glaforge.dev/posts/2015/05/26/groovy-weekly-71/</link><pubDate>Tue, 26 May 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/05/26/groovy-weekly-71/</guid><description>&lt;p>GR8Conf Europe 2015 is literally around the corner, as the conference will start next week in Copenhagen! I’m really excited to be joining the usual flock of Groovy afictionados and talk all things Groovy.&lt;/p>
&lt;p>Speaking of conferences, lots of new videos and accompanying slide decks have been released, so there are hours of binge watching ahead!&lt;/p>
&lt;p>By now, I think I can stop reminding you about the move of Groovy from Codehaus to the Apache Software Foundation? (and the move of the mailing-lists, issue tracker, sources, etc.)&lt;/p></description><content:encoded>
<![CDATA[<p>GR8Conf Europe 2015 is literally around the corner, as the conference will start next week in Copenhagen! I’m really excited to be joining the usual flock of Groovy afictionados and talk all things Groovy.</p>
<p>Speaking of conferences, lots of new videos and accompanying slide decks have been released, so there are hours of binge watching ahead!</p>
<p>By now, I think I can stop reminding you about the move of Groovy from Codehaus to the Apache Software Foundation? (and the move of the mailing-lists, issue tracker, sources, etc.)</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/602547059418386435">GrooScript 1.1.1</a> released with associated Gradle plugin with support for Require.JS for improved Groovy / JavaScript interoperability</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Ken Kousen recounts his <a href="https://kousenit.wordpress.com/2015/05/25/upcoming-events-and-the-streak/">upcoming Groovy-related events and activities</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="https://github.com/kdabir/awesome-groovy">Awesome Groovy crosses 100 stars</a> and counting on Github</li>
<li><a href="https://twitter.com/gvmtool/status/603123176315424768">GVM is thinking about changing its name</a>, cast your vote!</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/97">Grails Diary</a> week #21</li>
</ul>
<h2 id="presentations--special-greach-2015-coverage">Presentations — special Greach 2015 coverage</h2>
<ul>
<li><a href="http://greachconf.com/speakers/jorge-franco-grooscript-in-action/">Grooscript in action</a> by Jorge Franco</li>
<li>Andrés Almiray presented about the <a href="http://greachconf.com/speakers/andres-almiray-the-groovy-ecosystem/">Groovy ecosystem</a></li>
<li>Sport store chain <a href="http://greachconf.com/speakers/antonio-de-la-torre-alonso-torres-decathlon-sportmeeting-sports-a-new-grails-discipline/">Decathlon built its social network with Grails</a>, presented by Antonio de la Torre and Alonso Torres Ortiz</li>
<li><a href="http://greachconf.com/speakers/steve-pember-advanced-microservice-concerns/">Advanced microservice concerns</a> by Steve Pember</li>
<li><a href="http://greachconf.com/speakers/rene-groeschke-beyond-gradle-2-0/">Beyond Gradle 2.0</a> by René Gröschke</li>
<li><a href="http://greachconf.com/speakers/schalk-w-cronje-idiomatic-gradle-plugin-writing/">Idiomatic Gradle plugin writing</a> by Schalk Cronjé</li>
<li><a href="http://greachconf.com/speakers/rene-groeschke-building-android-apps-with-gradle/">Building Android apps with Gradle</a> by René Gröschke</li>
<li><a href="http://greachconf.com/speakers/markus-schlichting-documentation-brought-to-life-asciidoctor-gradle/">Documentation brought to life with Asciidoctor and Gradle</a> by Markus Schlichting</li>
<li>Marco Vermeulen gives a status update on the <a href="http://greachconf.com/speakers/marco-vermeulen-groovy-environment-manager-2015/">Groovy enVironment Manager in 2015</a></li>
<li><a href="http://greachconf.com/speakers/jeff-beck-securing-ratpack/">Securing Ratpack</a> by Jeff Beck</li>
<li>Göran Ehrsson on <a href="http://greachconf.com/speakers/goran-ehrsson-cut-your-grails-application-to-pieces-build-feature-plugins/">cutting your Grails app to pieces with feature plugins</a></li>
<li>Burt Beckwith shares <a href="http://greachconf.com/speakers/burt-beckwith-little-did-he-know/">interesting nuggets found during Grails development</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>With Codehaus&rsquo; EOL, <a href="https://twitter.com/gvmtool/status/601312032881844224">Groovy binary distributions prior to 2.2.2 are temporarily unavailable through GVM</a></li>
<li>Robin Bramley suggest an <a href="https://twitter.com/rbramley/status/601350754595639297">ideal Groovy snack for the break</a>!</li>
</ul>
<h2 id="snippets">Snippets</h2>
<ul>
<li>Erik Pragt shares a <a href="https://gist.github.com/bodiam/fe3f8448e8296bc2e07d">script to check the health status of a Grails 3 app</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/mittie/status/603286822396010496">Groovy in Action will have a total of 912 pages</a>!</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>A &ldquo;fintech&rdquo; startup in London is looking for <a href="https://twitter.com/Charliedysonrec/status/601496181106171904">developers with Groovy, Grails and mobile experience</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/CedricChampeau/status/603145483914215424">GR8Conf Android application has been updated</a> to display next week&rsquo;s European edition!</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #70</title><link>https://glaforge.dev/posts/2015/05/20/groovy-weekly-70/</link><pubDate>Wed, 20 May 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/05/20/groovy-weekly-70/</guid><description>&lt;p>Last week, Codehaus has gone the way of the dodo. The lights are switching off. A page is turning.&lt;/p>
&lt;p>So please remember about our infrastructure changes, as we move the Groovy project through the incubation process at Apache: the &lt;a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved&lt;/a>, and don’t forget that the &lt;a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving&lt;/a>. The &lt;a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository&lt;/a> is now the new mainline, but you can contribute pull requests on the new &lt;a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror&lt;/a>. Be sure to update all your bookmarks, and register on the new mailing-list!&lt;/p></description><content:encoded>
<![CDATA[<p>Last week, Codehaus has gone the way of the dodo. The lights are switching off. A page is turning.</p>
<p>So please remember about our infrastructure changes, as we move the Groovy project through the incubation process at Apache: the <a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved</a>, and don’t forget that the <a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving</a>. The <a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository</a> is now the new mainline, but you can contribute pull requests on the new <a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror</a>. Be sure to update all your bookmarks, and register on the new mailing-list!</p>
<p>Beside that, there’s one particular item in the news below that I’d like to highlight here, that’s the <a href="https://speakerdeck.com/szpak/smarter-testing-java-code-with-spock-framework">great and thourough presentation on Spock</a>. If you want to learn the latest and greatest about Spock 1.0, that’s the slide deck you must have a look at!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/AndreyHihlovski/status/600703585027018753">Gretty 1.2.3</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Igor Shults shares the <a href="https://objectpartners.com/2015/05/14/list-of-groovy-versions-for-each-version-of-grails/">list of Groovy versions for each version of Grails</a></li>
<li>Jacob Severson talks about <a href="https://objectpartners.com/2015/05/19/dependable-microservices-via-lattice/">dependable microservices via Cloud Foundry&rsquo;s Lattice and Grails 3</a></li>
<li>Brendon Anderson shares his <a href="https://objectpartners.com/2015/05/12/my-first-ratpack-app-what-i-learned/">feedback on his first Ratpack application</a></li>
<li>The Groovy-Programming blog post features an article on <a href="http://groovy-programming.com/post/118804818009">Google Analytics measurement protocol in Groovy</a></li>
<li>David Dawson explains how to <a href="http://www.infoq.com/presentations/hexagonal-arch-grails">build a Grails application based on a hexagonal architecture</a></li>
<li>The Groovy Algorithms blog features a post on a <a href="http://groovyalgorithms.altervista.org/frequent-pattern-mining-model-implementation/">model implementation of frequent pattern mining</a></li>
<li>Algorithms in Groovy blog post on the <a href="http://groovyalgorithms.altervista.org/top-5-things-that-make-me-love-scripting-in-groovy/">top 5 that makes Groovy scripting loveable</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/96">Grails Diary</a> week 20</li>
<li>The <a href="https://twitter.com/gvmtool/status/598961449193459712">Gradle GVM SDK vendor plugin</a> is now available in the Gradle plugin portal</li>
<li>The <a href="http://groovymn.tumblr.com/post/118772921517/last-month-in-gum">last month in GUM</a> news</li>
<li>The curated <a href="http://groovydevweekly.com/issues/5#start">Groovy Dev Weekly</a> news</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Marcin Zajączkowski gave an <a href="https://speakerdeck.com/szpak/smarter-testing-java-code-with-spock-framework">excellent and detailed presentation on smarter testing Java code with Spock</a>, with lots of Spock 1.0 coverage. A must see to master Spock!</li>
<li>From the GeeCon conference:
<ul>
<li>Iván López shares the <a href="https://twitter.com/ilopmar/status/599124700527087616">slides and code of his Grails presentation</a></li>
<li>Álvaro Sánchez-Mariscal shares the <a href="https://twitter.com/alvaro_sanchez/status/599178631827587072">slides and code of his Ratpack presentation and workshop</a></li>
</ul>
</li>
<li>From Devoxx France (but in French!):
<ul>
<li>“<a href="https://www.parleys.com/tutorial/plugin-gradle-prenez-le-controle-du-build">Plugin Gradle, prenez le contrôle du build</a>”, by Cédric Champeau and Eyal Lezmy</li>
<li>&ldquo;<a href="https://www.parleys.com/tutorial/groovy-et-son-process-de-release-nous-lavons-rendu-grooooooovy">Groovy et son process de release</a>&rdquo; by Guillaume Laforge, Cédric Champeau and Frédéric Simon</li>
</ul>
</li>
<li>From Greach, <a href="http://greachconf.com/speakers/alvaro-sanchez-mariscal-stateless-authentication-for-microservices/">stateless authentication for microservices with Spring Security in Grails</a>, by Álvaro Sánchez-Mariscal</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Ken Kousen published a <a href="https://twitter.com/kenkousen/status/600725847826366464">Groovy Fundamentals video course</a> for O&rsquo;Reilly Media</li>
<li>Craig Burke believes that <a href="https://twitter.com/craigburke1/status/598883871330062336">Grails 3 custom scripts can prove useful for generating Grails / Angular.JS CRUD apps</a></li>
<li>Craig Burke <a href="https://twitter.com/craigburke1/status/598912886031912962">upgraded his Angular.JS / Grails test app to Grails 3</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Dierk König says that <a href="https://twitter.com/mittie/status/600199545335771136">Groovy in Action 2nd ed offers the first full description of Groovy as a static language</a>, covering static compilation, static type checking, and type checking extensions</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The Gradle summit early bird ends Friday</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #69</title><link>https://glaforge.dev/posts/2015/05/12/groovy-weekly-69/</link><pubDate>Tue, 12 May 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/05/12/groovy-weekly-69/</guid><description>&lt;p>Groovy Weekly skipped a beat as I was very busy: I was very fortunate to spend my last week in California for various meetings, plus being a keynote speaker at JFrog’s &lt;a href="http://swampup.jfrog.com/">SwampUp&lt;/a> conference in Napa Valley. I had the chance to speak again about the Groovy release process and its history (based on my recent Devoxx &lt;a href="https://speakerdeck.com/glaforge/groovys-release-process-devoxx-2015">talk&lt;/a>).&lt;/p>
&lt;p>Congrats to the Gradle team for releasing &lt;a href="http://swampup.jfrog.com/">Gradle 2.4 with much faster builds&lt;/a>!&lt;/p>
&lt;p>Hurry up to &lt;a href="http://us4.campaign-archive2.com/?u=ac7af4c02d6cec67fe3198a63&amp;amp;id=6071c3f248">register to GR8Conf Europe 2015&lt;/a>, as registration closes on May 15th! Hurry up, and join the Groovy ecosystem fans gathering!&lt;/p></description><content:encoded>
<![CDATA[<p>Groovy Weekly skipped a beat as I was very busy: I was very fortunate to spend my last week in California for various meetings, plus being a keynote speaker at JFrog’s <a href="http://swampup.jfrog.com/">SwampUp</a> conference in Napa Valley. I had the chance to speak again about the Groovy release process and its history (based on my recent Devoxx <a href="https://speakerdeck.com/glaforge/groovys-release-process-devoxx-2015">talk</a>).</p>
<p>Congrats to the Gradle team for releasing <a href="http://swampup.jfrog.com/">Gradle 2.4 with much faster builds</a>!</p>
<p>Hurry up to <a href="http://us4.campaign-archive2.com/?u=ac7af4c02d6cec67fe3198a63&amp;id=6071c3f248">register to GR8Conf Europe 2015</a>, as registration closes on May 15th! Hurry up, and join the Groovy ecosystem fans gathering!</p>
<p>Reminder about our infrastructure changes, as we move the Groovy project through the incubation process at Apache: the <a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved</a>, and don’t forget that the <a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving</a>. The <a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository</a> is now the new mainline, but you can contribute pull requests on the new <a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror</a>. Be sure to update all your bookmarks, and register on the new mailing-list!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://discuss.gradle.org/t/gradle-2-4-released/9471">Gradle 2.4</a> released</li>
<li><a href="http://ratpack.io/versions/0.9.16/">Ratpack 0.9.16</a> released</li>
<li><a href="http://www.craigburke.com/document-builder/">Groovy Document Builder v0.4</a> by Craig Burke</li>
<li><a href="http://grengine.ch/manual.html#grengine-and-grape">Grengine 1.0.3</a> released</li>
<li><a href="http://www.jexler.net">Jexler 1.0.16</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Mirosław Gołda on <a href="http://allegrotech.io/automated-tests-with-geb-spock-and-groovy.html">automated tests with Geb, Spock and Groovy</a></li>
<li>MrHaki&rsquo;s Groovy Goodness: <a href="http://mrhaki.blogspot.dk/2015/05/groovy-goodness-share-data-in.html">Share data in concurrent environment with dataflow variables</a></li>
<li><a href="http://blog.agileorbit.com/2015/05/11/Grails3-OAuth2.html">APIs with Grails 3 and OAuth 2</a> by Bobby Warner</li>
<li><a href="https://kousenit.wordpress.com/2015/05/08/concurrent-kitties-using-gpars/">Concurrent kitties using GPars</a> by Ken Kousen (spoiler, no animals harmed!)</li>
<li><a href="http://grooscript.org/gradle/templating.html">GrooScript Groovy templating on the browser client-side</a></li>
<li>Eric MacAdie writes about the <a href="http://www.macadie.net/2015/05/09/hidden-annotation-in-groovy-validators/">@Hidden annotation in Groovy Validators</a></li>
<li><a href="https://discuss.gradle.org/t/testing-plugin-compatibility-against-gradle-versions/9473">Testing plugin compatibility against Gradle versions</a> by Schalk Cronjé on the Gradle forums</li>
<li><a href="http://www.dahlgren.so/docker/2015/04/12/Deploying-Grails-Apps-To-Docker/">Deploying Grails apps to Docker</a> by Ron Dahlgren</li>
<li><a href="http://groovyalgorithms.altervista.org/groovy-profiling-with-gprof/">Groovy profiling with Gprof</a></li>
<li><a href="http://groovyalgorithms.altervista.org/groovy-benchmarking-with-gbench/">Groovy benchmarking with GBench</a></li>
<li><a href="https://objectpartners.com/2015/04/29/using-gradle-and-bower-to-manage-jscss-dependencies/">Using Gradle and Bower to manage JavaScript and CSS dependencies</a> by Patrick Double</li>
<li><a href="http://www.intelligrape.com/blog/compile-groovyscript-at-runtime-allow-caching-of-compiled-source-to-avoid-recompilation-at-runtime-using-groovyclassloader/">Compile Groovy scripts at runtime and allow caching</a> of compiled source to avoid recompilation using GroovyClassLoader by Tarun Pareek</li>
<li>StackOverflow Groovy superstar Tim Yates answers a question on <a href="http://stackoverflow.com/questions/29976665/why-groovys-map-scales-better-than-array">map vs array performance</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://grydeske.net/news/show/95">Grails Diary</a> week 19 by Jakob Aae Mikkelsen</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Cédric Champeau presented at the Bordeaux JUG on Gradle&rsquo;s way of convention over configuration</li>
<li>More SpringOne2GX 2014 videos released:
<ul>
<li><a href="http://www.infoq.com/presentations/grails-plugin-testing">Grails plugin testing strategies</a> by Baruch Sadogursky</li>
<li>Owen Rubel talks about <a href="http://www.infoq.com/presentations/api-grails-dry">API abstraction and API chaining in Grails</a></li>
<li><a href="http://www.infoq.com/presentations/groovy-rest-nosql">Groovy vampires: combining Groovy, REST, NoSQL</a>, and more by Ken Kousen</li>
<li>Fabrice Matrat dives into <a href="http://www.infoq.com/presentations/single-page-app-ravejs-grails">single page applications with Grails and RaveJS</a></li>
<li><a href="http://www.infoq.com/presentations/web-mobile-apps-spring-groovy">Conquering content-enabled web and mobile applications with Spring and Groovy</a> by Russ Danner</li>
<li><a href="http://www.infoq.com/presentations/ast-transformations">Writing AST transformations, get practical in 90 minutes</a> by Baruch Sadogursky and Fred Simon</li>
<li>The <a href="http://www.infoq.com/presentations/future-gradle-javascript">future of Gradle</a>, the ultimate build system, by Hans Dockter</li>
</ul>
</li>
<li>Greach videos too:
<ul>
<li>Trisha Gee on <a href="http://java.dzone.com/articles/groovy-vs-java-testing-video">Groovy vs Java for testing</a></li>
<li><a href="http://greachconf.com/speakers/ruben-mondejar-andreu-introducing-workflow-architectures-using-grails/">Introducing workflow architectures using Grails</a> by Rubén Mondéjar Andreu</li>
<li><a href="http://greachconf.com/speakers/jeff-beck-grails-and-cassandra/">Grails and Cassandra</a> by Jeff Beck</li>
<li><a href="http://greachconf.com/speakers/alexander-sascha-klein-groovy-on-the-shell/">Groovy on the shell</a> by Alexander Klein</li>
<li><a href="http://greachconf.com/speakers/fatima-casau-use-groovy-grails-in-your-spring-boot-projects-don-t-be-afraid/">Use Groovy and Grails in your Spring Boot projects</a>, by Fatima Casau</li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau echoes that the <a href="https://twitter.com/CedricChampeau/status/593347714244575232">Groovy project is looking for developers to help on the Groovy Eclipse project</a></li>
<li>Dierk König says that Groovy in Action chapter 8 is the first <a href="https://twitter.com/mittie/status/597649018676707328">complete exposition of all Groovy runtime metaprogramming capabilities</a></li>
<li>The <a href="https://twitter.com/mittie/status/596753493270110208">code base of Groovy in Action 2nd ed contains over 1000 power assert statements</a> counted Dierk König</li>
<li><a href="https://twitter.com/theaviary/status/597053097089826817">Griffon 2.3 will feature JavaFX specific AST transformations</a></li>
<li>Ratpack 0.9.17 will let you use <a href="https://twitter.com/ratpackweb/status/594753885266378752">async promises from synchronous APIs</a></li>
<li>Robert Zakrzewski is a <a href="https://twitter.com/ratpackweb/status/595515092885643264">new contributor to the Ratpack</a> project</li>
<li><a href="https://twitter.com/gvmtool/status/595553687617036289">Gradle 2.4</a> available on GVM</li>
<li><a href="https://twitter.com/aheritier/status/595671992797388800">SonarQube&rsquo;s Groovy plugin now supports code coverage</a> notes Arnaud Héritier</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=E36Dw5xbhzQ">Groovy podcast</a> episode 12 is out</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Bertrand Goetzmann published a screencast about <a href="https://www.youtube.com/watch?v=LI6EIfmt26I">creating micro services with Grails 3</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau shows how he integrated <a href="https://github.com/melix/bdxjug2015/blob/master/gradle/pdf.gradle">deck2pdf into his Gradle build</a></li>
<li>Code samples for <a href="http://asoftwareguy.com/2015/03/30/spring-security-with-grails-3/">Grails 3 and Spring Security</a></li>
<li>Sergio del Amo authored a <a href="https://github.com/sdelamo/wp-api-groovy">Groovy script to interact with Wordpress&rsquo; JSON REST API</a></li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>New Grails&rsquo; home <a href="https://twitter.com/grailsframework/status/596081391013466113">Object Computing is hiring more Grails experts</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="http://us4.campaign-archive2.com/?u=ac7af4c02d6cec67fe3198a63&amp;id=6071c3f248">GR8Conf Europe 2015 registration is closing May 15th</a>, be fast!</li>
<li>You can see all the <a href="https://twitter.com/greachconf/status/595855035117895680">speakers interviews from Greach</a> on YouTube</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #68</title><link>https://glaforge.dev/posts/2015/04/28/groovy-weekly-68/</link><pubDate>Tue, 28 Apr 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/04/28/groovy-weekly-68/</guid><description>&lt;p>Remember the Android support of Groovy 2.4? Well, the new &lt;a href="https://twitter.com/mohitpandey/status/592723947311001600">New York Times Android application, written in Groovy&lt;/a> and using RxJava is now live! If a hugely popular Android app is leveraging Groovy, yours can too!&lt;/p>
&lt;p>With Groovy’s move to the Apache Software Foundation, &lt;a href="https://twitter.com/sbglasius/status/591322009214160896">don’t forget to star the new Groovy Github repository&lt;/a>!&lt;/p>
&lt;p>Don’t miss the interesting interviews of &lt;a href="http://www.infoq.com/news/2015/04/grails-3">Jeff Brown&lt;/a> and &lt;a href="https://www.voxxed.com/blog/2015/04/pushing-groovy-to-the-speed-of-java-and-beyond/">Guillaume Laforge&lt;/a>.&lt;/p>
&lt;p>Reminder about our infrastructure changes, as we move the Groovy project through the incubation process at Apache: the &lt;a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved&lt;/a>, and don’t forget that the &lt;a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving&lt;/a>. The novelty this week is that the &lt;a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository&lt;/a> is now the new mainline, but you can contribute pull requests on the &lt;a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>Remember the Android support of Groovy 2.4? Well, the new <a href="https://twitter.com/mohitpandey/status/592723947311001600">New York Times Android application, written in Groovy</a> and using RxJava is now live! If a hugely popular Android app is leveraging Groovy, yours can too!</p>
<p>With Groovy’s move to the Apache Software Foundation, <a href="https://twitter.com/sbglasius/status/591322009214160896">don’t forget to star the new Groovy Github repository</a>!</p>
<p>Don’t miss the interesting interviews of <a href="http://www.infoq.com/news/2015/04/grails-3">Jeff Brown</a> and <a href="https://www.voxxed.com/blog/2015/04/pushing-groovy-to-the-speed-of-java-and-beyond/">Guillaume Laforge</a>.</p>
<p>Reminder about our infrastructure changes, as we move the Groovy project through the incubation process at Apache: the <a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved</a>, and don’t forget that the <a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving</a>. The novelty this week is that the <a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository</a> is now the new mainline, but you can contribute pull requests on the <a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror</a>.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://discuss.gradle.org/t/gradle-2-4-rc-1-is-now-available-for-testing/9282">Gradle 2.4-rc-1</a> released with annotation processing with Groovy code</li>
<li>Online presentation service Prezi releases a <a href="https://twitter.com/dvigovszky/status/590889954655473664">Haskell plugin for Gradle</a></li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>MrHaki’s on the writing spree:
<ul>
<li>Grails Goodness
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/04/grails-goodness-adding-health-check.html">adding health check indicators</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/04/grails-goodness-log-startup-info.html">log startup info</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/04/grails-goodness-save-application-pid-in.html">save application PID in file</a></li>
</ul>
</li>
<li>Gradle Goodness
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/04/gradle-goodness-handle-copying.html">handle copying duplicate files</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/04/gradle-goodness-use-git-commit-id-in.html">usee Git commit id in build script</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="http://www.grails.info/2015/04/23/new-blog-groovy-now-runs-faster-than-java-on-mobile/">Groovy now runs faster on mobile</a> than Java says Grails Info news site</li>
<li>Iván López on <a href="http://www.kaleidos.net/blog/1071/talking-about-groovy-in-vienna/">talking about Groovy</a> in Vienna</li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Guillaume Laforge interviewed by Voxxed during Devoxx France, talking about <a href="https://www.voxxed.com/blog/2015/04/pushing-groovy-to-the-speed-of-java-and-beyond/">Groovy&rsquo;s Android support, Grails 3.0, Groovy in RESTful APIs</a> and more</li>
<li>Matt Raible interview <a href="http://www.infoq.com/news/2015/04/grails-3">Jeff Brown on Grails 3</a>, built on Spring Boot and Gradle</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="https://twitter.com/mohitpandey/status/592723947311001600">New York Times Android application written in Groovy</a> and using RxJava is now live!</li>
<li><a href="http://www.itexto.net/devkico/?p=2156">Grails Brasil launches its new website</a>, promising to open source its code base so other user groups can benefit from it</li>
<li><a href="https://twitter.com/gvmtool/status/592940974470537216">Gradle 2.4-rc-1</a> available on GVM</li>
<li>Gradle 2.4-rc-1 release notes covers <a href="https://gradle.org/docs/2.4-rc-1/release-notes#support-for-%E2%80%9Cannotation-processing%E2%80%9D-of-groovy-code">annotation processing of Groovy code</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="http://fr.slideshare.net/nareshak/pragmatic-browser-automation-with-geb-gids-2015">Programmatic browser automation with Geb</a> by Naresha K at the Great Indian Developer Summit</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Wtih Groovy moving to Apache, so did its Github repository. <a href="https://twitter.com/sbglasius/status/591322009214160896">Don&rsquo;t forget to star the new Groovy repo on Github</a>!</li>
<li>Dagger developer Jake Wharton mentions <a href="https://twitter.com/JakeWharton/status/591242052202934273">support for annotation in Groovy code via generated stubs with Gradle 2.4</a></li>
<li><a href="https://twitter.com/grooscript/status/591713872568033282">GrooScript moved its Groovy Ecosystem demo</a> to a new location</li>
<li>Voxxed mentions <a href="https://twitter.com/voxxed/status/591231196245417984">Groovy moving to the speed of Java</a></li>
<li>Dierk König mentions that the script of <a href="https://twitter.com/mittie/status/592937821402443776">Groovy in Action, 2nd edition, has over 1600 commits</a> on its private Git repository</li>
<li><a href="https://twitter.com/gvmtool/status/591149663782359040">Lazybones 0.8.1</a> available on GVM</li>
<li>Dan Woods highlights Ratpack 0.9.15&rsquo;s Groovy extensions <a href="https://twitter.com/danveloper/status/590950625254354944">easily transform RxJava Observables into Ratpack Promises</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://twitter.com/groovypodcast/status/590970033410547713">Groovy podcast episode 11</a> available</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>The NetBeans team recorded a screencast about Minecraft Forge showcasing its <a href="https://twitter.com/netbeans/status/592059252702851072">Gradle support</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/mittie/status/591272887786569730">Groovy in Action, 2nd edition, now in proof-reading stage</a>!</li>
<li>New book, in Portuguese, “<a href="http://www.casadocodigo.com.br/products/livro-grails">Falando de Grails</a>”</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li><a href="https://gist.github.com/jexp/7c6997242c001abfb2cd">Load JSON data into Neo4J with Groovy</a> and Cypher</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>A <a href="https://twitter.com/sbglasius/status/590575191119507459">job for beer lovers and Grails experts</a></li>
</ul>
<h2 id="google-posts">Google+ posts</h2>
<ul>
<li>New improvements to Tim Roes’ <a href="https://plus.google.com/u/0/+TimRoes/posts/f5LrmstNqTE?cfem=1">Groovy Playground</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>GR8Conf Europe will feature <a href="https://twitter.com/gr8conf/status/592899671519027201">more Groovy puzzlers</a></li>
<li><a href="https://twitter.com/gr8conf/status/592898886848671747">Grails event driven plugins</a> at GR8Conf Europe by Göran Ehrsson</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #67</title><link>https://glaforge.dev/posts/2015/04/21/groovy-weekly-67/</link><pubDate>Tue, 21 Apr 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/04/21/groovy-weekly-67/</guid><description>&lt;p>No big focus this week in the editorial, but as in the previous edition, I’d like to remind you about the following infrastructure changes, as we move the Groovy project through the incubation process at Apache: the &lt;a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved&lt;/a>, and don’t forget that the &lt;a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving&lt;/a>. The novelty this week is that the &lt;a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository&lt;/a> is now the new mainline, but you can contribute pull requests on the &lt;a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>No big focus this week in the editorial, but as in the previous edition, I’d like to remind you about the following infrastructure changes, as we move the Groovy project through the incubation process at Apache: the <a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved</a>, and don’t forget that the <a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving</a>. The novelty this week is that the <a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git repository</a> is now the new mainline, but you can contribute pull requests on the <a href="https://twitter.com/glaforge/status/588960808094326784">Apache Groovy Github mirror</a>.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/589123058318860290">GrooScript 1.0.2</a> released</li>
<li><a href="https://github.com/akhikhl/gretty/blob/master/RELEASE.md#gretty-122-release-announcement">Gretty 1.2.2</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Paul Krill of InfoWorld on <a href="http://www.cio.com/article/2909687/developer/grails-web-framework-finds-home-at-object-computing.html">Grails finding a new home at OCI</a></li>
<li>An overview of <a href="http://java.dzone.com/articles/whats-new-grails-3">what&rsquo;s new in Grails 3</a> by Michael Scharhag</li>
<li>Geb Gems: <a href="http://blog.jdriven.com/2015/04/geb-gems-running-geb-spock-tests-maven/">Running Geb Spock tests with Maven</a> by Albert van Veen</li>
<li>Jeff Beck on <a href="http://beckje01.com/blog/2015/04/18/springsecurity-impersonate-users-custom-roles/">impersonating user custom roles with Spring Security in Grails</a></li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.dk/2015/04/gradle-goodness-alter-start-scripts.html">alter start scripts from application plugin</a></li>
<li>MrHaki&rsquo;s Grails Goodness: <a href="http://mrhaki.blogspot.fr/2015/04/grails-goodness-set-log-level-for.html">set log level for Grails artifacts</a></li>
<li>MrHaki&rsquo;s Grails Goodness: <a href="http://mrhaki.blogspot.fr/2015/04/grails-goodness-add-some-color-to-our.html">add some color to our logging</a></li>
<li><a href="http://www.intelligrape.com/blog/making-a-domain-non-persistent/">Making a Grails domain class non-persistent</a> by Vinay Prajapati</li>
<li><a href="http://www.intelligrape.com/blog/parsing-url-mappings-in-grails/">Parsing URL mappings in Grails</a> by Sandeep Poonia</li>
<li><a href="http://www.intelligrape.com/blog/spring-events-with-grails/">Spring events with Grails</a></li>
<li>Ken Kousen writes <a href="https://kousenit.wordpress.com/2015/04/16/groovy-posts-in-other-places/">Groovy related blog posts</a> elsewhere too</li>
<li><a href="https://weblogs.java.net/blog/manningpubs/archive/2013/03/18/building-java-web-application-gradle">Building a Java web application with Gradle</a> by Benjamin Muschko</li>
<li>Short on time? Switch to <a href="http://www.javacodegeeks.com/2015/04/short-on-time-switch-to-groovy-for-unit-testing.html">Groovy and Spock for unit testing</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="https://gradle.org/welcoming-lari-hotari-and-cedric-champeau-to-gradle-team/">Gradleware is officially welcoming Cédric Champeau and Lari Hotari</a> into the Gradle team</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/92">Grails Diary</a> week 16</li>
<li>You can still <a href="https://youtrack.jetbrains.com/issue/IDEA-136970">vote for Grails 3 support in IntelliJ IDEA</a></li>
<li>A <a href="http://groovydevweekly.com/issues/1?#start">new curated list of Groovy news</a> by Sergio del Amo</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="http://www.infoq.com/presentations/groovy-grails-puzzler">Groovy and Grails puzzlers</a>, traps, pitfalls, and end cases by Baruch Sadogursky and Frédéric Simon, recorded at SpringOne2GX 2015</li>
<li>Guillaume Laforge shares the links to his <a href="https://glaforge.dev/talks/2015/04/17/devoxx-2015-presentations/">Devoxx France presentations on Groovy</a></li>
<li><a href="https://www.youtube.com/watch?v=3pdFdbvaUyg">Confessions of a Java developer who fell in love with Groovy</a> (in Hebrew)</li>
<li>Iván López on <a href="https://twitter.com/ilopmar/status/588338534366457856">metaprogramming with Groovy</a> at the Confess conference with slides and sample code</li>
<li>Alexander Klein on <a href="http://fr.slideshare.net/sascha_klein/groovy-on-the-shell">Groovy in the Shell</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>The <a href="https://twitter.com/GebFramework/status/588457692651356160">Geb framework developers are happy with their move to Github issues</a></li>
<li>Guillaume Laforge shares the new <a href="https://twitter.com/glaforge/status/588673146355613696">Apache Groovy mailing-list archives</a> links</li>
<li>Guillaume Laforge gives the link to the <a href="https://twitter.com/glaforge/status/588809178958925824">Apache Git web interface</a></li>
<li>You can <a href="https://twitter.com/glaforge/status/588960808094326784">send pull requests to Apache&rsquo;s Groovy git mirror on Github</a> notes Guillaume Laforge</li>
<li>Søren Berg Glasius reminds developers they can now <a href="https://twitter.com/sbglasius/status/589088682512027648">star the Groovy git mirror on Github</a> for good reputation</li>
<li><a href="https://twitter.com/CedricChampeau/status/588968404633157632">Cédric Champeau received his JavaOne Rock Star award</a>!</li>
<li>Buildship, a <a href="https://twitter.com/Gradleware/status/589102675381379072">Gradle plugin for Eclipse</a> by GradleWare and Vogella</li>
<li>Craig Burke is having fun with <a href="https://twitter.com/craigburke1/status/589107422754693120">laying out tables in his Groovy Document Builder for v0.4</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/gr8conf/status/588319368477458432">Object Computing becomes GR8Conf Europe gold sponsor</a></li>
<li>The <a href="https://twitter.com/JacobAae/status/589112241426931713">GR8Conf Europe beers are ready</a>!</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Devoxx 2015 Presentations</title><link>https://glaforge.dev/talks/2015/04/17/devoxx-2015-presentations/</link><pubDate>Fri, 17 Apr 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2015/04/17/devoxx-2015-presentations/</guid><description>&lt;p>I had the pleasure of presenting 3 talks at Devoxx France last week (plus the Cast Codeurs podcast live recording), and I wanted to share with you the slides of those presentations.&lt;/p>
&lt;h2 id="the-groovy-release-process">The Groovy release process&lt;/h2>
&lt;p>With Cédric Champeau and Frédéric Simon&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="e3398ca68b9d4ef2a65309f92bb10d16" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;h2 id="groovy-with-style">Groovy with style&lt;/h2>
&lt;script async class="speakerdeck-embed" data-id="d46bba738e3a469281caecb3a68803b9" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script>
&lt;h2 id="machine-learning-introduction">Machine Learning introduction&lt;/h2>
&lt;p>In French, with Didier Girard&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="20d59cbfef164757baf7599d5e88c141" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js">&lt;/script></description><content:encoded>
<![CDATA[<p>I had the pleasure of presenting 3 talks at Devoxx France last week (plus the Cast Codeurs podcast live recording), and I wanted to share with you the slides of those presentations.</p>
<h2 id="the-groovy-release-process">The Groovy release process</h2>
<p>With Cédric Champeau and Frédéric Simon</p>
<script async class="speakerdeck-embed" data-id="e3398ca68b9d4ef2a65309f92bb10d16" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="groovy-with-style">Groovy with style</h2>
<script async class="speakerdeck-embed" data-id="d46bba738e3a469281caecb3a68803b9" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<h2 id="machine-learning-introduction">Machine Learning introduction</h2>
<p>In French, with Didier Girard</p>
<script async class="speakerdeck-embed" data-id="20d59cbfef164757baf7599d5e88c141" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #66</title><link>https://glaforge.dev/posts/2015/04/14/groovy-weekly-66/</link><pubDate>Tue, 14 Apr 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/04/14/groovy-weekly-66/</guid><description>&lt;p>We’re only mid April but what a year it’s been so far! From &lt;a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">Groovy &amp;amp; Grails’ loss of funding&lt;/a>, leading &lt;a href="http://interact.stltoday.com/pr/business/PR040915114421060">Grails to a new home at Object Computing Inc.&lt;/a> and &lt;a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">Groovy at the Apache Software Foundation&lt;/a>, to &lt;a href="http://restlet.com/blog/2015/03/02/head-of-groovy-project-joins-restlet-to-lead-api-development-tools/">Guillaume Laforge joining Restlet&lt;/a> and &lt;a href="https://twitter.com/cedricchampeau/">Cédric joining GradleWare&lt;/a>. Against all odds, &lt;a href="https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/">Groovy released its 2.4 version with the Android support&lt;/a>, and the big &lt;a href="http://grails.github.io/grails-doc/3.0.x/guide/introduction.html#whatsNew">3.0 release of Grails&lt;/a> saw the light of day. And on another personal note… I’ve &lt;a href="https://instagram.com/p/1YIA2KANZD/">finished my first marathon&lt;/a>! What’s next for the rest of the year?&lt;/p></description><content:encoded>
<![CDATA[<p>We’re only mid April but what a year it’s been so far! From <a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">Groovy &amp; Grails’ loss of funding</a>, leading <a href="http://interact.stltoday.com/pr/business/PR040915114421060">Grails to a new home at Object Computing Inc.</a> and <a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">Groovy at the Apache Software Foundation</a>, to <a href="http://restlet.com/blog/2015/03/02/head-of-groovy-project-joins-restlet-to-lead-api-development-tools/">Guillaume Laforge joining Restlet</a> and <a href="https://twitter.com/cedricchampeau/">Cédric joining GradleWare</a>. Against all odds, <a href="https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/">Groovy released its 2.4 version with the Android support</a>, and the big <a href="http://grails.github.io/grails-doc/3.0.x/guide/introduction.html#whatsNew">3.0 release of Grails</a> saw the light of day. And on another personal note… I’ve <a href="https://instagram.com/p/1YIA2KANZD/">finished my first marathon</a>! What’s next for the rest of the year?</p>
<p>With the Greach conference just over, there’s a lot of great and groovy content available in the presentations section! Take your time to have a look at this impressive lineup and interesting material.</p>
<p>Speaking of conferences, rush to buy early bird tickets for GR8Conf Europe! You only have till tomorrow!</p>
<p>As in the previous edition, I’d like to remind you about the following infrastructure changes, as we move the Groovy project through the incubation process at Apache: the <a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved</a>, and don’t forget that the <a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving</a>.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/MGrzejszczak/status/583875742053879808">Spock Subjects Collaborators Extension 1.1.0</a> released by Marcin Grzejszczak</li>
<li><a href="https://twitter.com/craigburke1/status/585611941072211969">Document Builder 0.3.1</a> released by Craig Burke with spacing and positioning fixes</li>
<li><a href="https://twitter.com/AndreyHihlovski/status/586809300678483969">Gretty 1.2.1</a> released with WebSocket support</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Ken Kousen writes about <a href="https://www.accelebrate.com/blog/kicking-ast-taking-names/">kicking AST and taking names</a></li>
<li>MrHaki&rsquo;s <a href="http://mrhaki.blogspot.fr/2015/04/greach-2015-conference-report.html">Greach conference report</a></li>
<li>Jochen Theodorou on <a href="http://blackdragsview.blogspot.de/2015/04/about-being-paid-oss-developer-for.html">being a paid OSS developer for Groovy</a></li>
<li><a href="http://interact.stltoday.com/pr/business/PR040915114421060">Grails has a new home at OCI</a></li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Cédric Champeau interviewed by Voxxed during the Devoxx France conference: &ldquo;<a href="https://www.voxxed.com/blog/2015/04/cedric-champeau-groovy-is-everybodys-language-to-evolve/">Groovy is everybody&rsquo;s language to evolve</a>&rdquo;</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="https://twitter.com/groovypodcast/status/585446151601704960">Groovy podcast now has a pretty nice logo</a>!</li>
<li><a href="https://twitter.com/GebFramework/status/587525108882866177">Geb&rsquo;s issue tracker moved to Github</a></li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>If you or <a href="https://twitter.com/CedricChampeau/status/587353266457018368">your company is interested in helping or taking over the development of the Groovy Eclipse plugin</a>, please tell us!</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Devoxx France 2015
<ul>
<li>Guillaume Laforge presented <a href="https://speakerdeck.com/glaforge/groovy-with-style-devoxx-2015">Groovy with Style</a></li>
<li>Cédric Champeau, Guillaume Laforge and Frédéric Simon from JFrog presented the <a href="https://speakerdeck.com/glaforge/groovys-release-process-devoxx-2015">Groovy release process</a></li>
<li><a href="https://docs.google.com/presentation/d/1llWSoa8KepAnYGFDE0HA4FtNmSdnVVHp7HPkQ5Ah94k/edit">Take control of your build with Gradle plugins</a>, presented at Devoxx France 2015 by Cédric Champeau and Eyal Lezmy</li>
</ul>
</li>
<li>Greach Conference
<ul>
<li><a href="http://fr.slideshare.net/JorgeFrancoLeza/grooscript-greach-2015">GrooScript in action</a> at Greach by Jorge Franco Leza</li>
<li>Overview of the <a href="http://fr.slideshare.net/ilopmar/greach-2015-ast-groovy-transformers-more-than-meets-the-eye">Groovy AST Transformers</a>: More than meets the eye! by Iván López at Greach 2015</li>
<li>Markus Schlichting brought <a href="http://fr.slideshare.net/madmas/documentation-brought-to-life-asciidoctor-gradle-greach-2015">documentation to life with Asciidoctor and Gradle</a></li>
<li>Burt Beckwith <a href="http://fr.slideshare.net/burtbeckwith/hacking-the-grails-spring-security-20-plugin">hacks the Grails Spring Security 2.0 plugin</a></li>
<li>Russel Winder talked about <a href="https://twitter.com/russel_winder/status/586550273855922176">GPars remoting</a> and shares his slides and associated code, at Greach Conference</li>
<li>Alonso Torres talked about <a href="http://fr.slideshare.net/alotor/greach-2015-decathlon-sport-meeting">Decathlon Sport Meeting application based on Grails</a></li>
<li><a href="http://fr.slideshare.net/fatimacasau/use-groovy-grails-in-your-spring-boot-projects">Use Groovy and Grails in your Spring Boot projects</a> by Fátima Casaú at Greach 2015</li>
<li>MrHaki shares the code of his Greach conference <a href="https://github.com/mrhaki/greach2015-grails-goodness">Grails Goodness</a> talk</li>
<li>Jeff Beck on <a href="http://beckje01.com/talks/greach-2015-cassandra-grails.html#/">Cassandra and Grails</a></li>
<li><a href="http://beckje01.com/talks/greach-2015-sec-ratpack.html#/">Securing Ratpack</a> by Jeff Beck</li>
<li>Jenn Strater on <a href="https://github.com/jlstrater/No-Nonsense-NoSQL">no nonsense NoSQL</a></li>
<li>Jenn Strater guided a discussion about <a href="https://twitter.com/JennStrater/status/587627413825318912">teaching and diversity in the Groovy community</a></li>
<li>Marco Vermeulen on <a href="http://marcovermeulen.github.io/gvm-talk/#/">GVM</a></li>
<li><a href="http://fr.slideshare.net/alotor/greach-2015-dsling-your-groovy">DSL&rsquo;ing your Groovy</a> by Alonso Torres</li>
<li><a href="http://fr.slideshare.net/aestasit/infrastructure-automation-with-gradle-and-puppet-at-greach-2015">Infrastructure automation with Gradle and Puppet</a> by Andrey Adamovich</li>
<li>Steve Pember covers <a href="http://fr.slideshare.net/StevePember/groovy-options-for-reactive-applications-greach-2015">Groovy options for reactive applications</a></li>
<li><a href="http://fr.slideshare.net/ysb33r/idiomatic-gradle-plugin-writing">Idiomatic Gradle plugin writing</a> by Schalk Cronjé at Greach 2015</li>
<li>Henrique Lobo presented about <a href="http://www.infoq.com/br/presentations/grails-alta-produtividade-em-java">how to obtain high productivity and quality with Groovy and Grails</a> (in Portuguese)</li>
<li><a href="http://www.infoq.com/presentations/tdd-cucumber-groovy-grails">Behaviour Driven Development with Cucumber, Groovy and Grails</a> by Marco Vermeulen, recorded at SpringOne2GX 2014</li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>The <a href="https://twitter.com/grailsframework/status/586263420195975169">Grails Framework is announcing having found a new home</a> at OCI</li>
<li>A <a href="https://twitter.com/greachconf/status/586806872134549504">cool Groovy T-Shirt</a> at Greach conference</li>
<li>Schalk Cronjé draws <a href="https://twitter.com/ysb33r/status/586818696737386496">GrooScript in a nutshell</a></li>
<li>Schalk Cronjé draws <a href="https://twitter.com/ysb33r/status/586843897793552384">GVM in a nutshell</a> from Marco Vermeulen Greach presentation</li>
<li>Iván López says that <a href="https://twitter.com/ilopmar/status/586827597469745153">only the &ldquo;real&rdquo; developers create AST transformations using emacs</a></li>
<li>A <a href="https://twitter.com/gvmtool/status/586892208860704768">new GVM logo in the works</a> by Alexander Klein?</li>
<li>Andrey Hihlovskiy on the <a href="https://twitter.com/AndreyHihlovski/status/587199044591714304">pain of Java&rsquo;s parentheses and semicolons</a> as the opposite of syntax sugar</li>
<li>Jonas Havers is impressed with <a href="https://twitter.com/JonasHavers/status/587245765736718336">Groovy&rsquo;s Markup Template Engine in combination with Spring Boot</a></li>
<li>The <a href="https://twitter.com/aheritier/status/587883820654944256">Groovy boys and JFrog are making the show</a> on stage at Devoxx France 2015</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://twitter.com/groovypodcast/status/586556048305213440">Groovy podcast episode 10</a> is available</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>John Engelman shares a Gradle tip on how to <a href="https://gist.github.com/johnrengelman/9a20697b2246a9bfaca2">apply a custom plugin on its own project</a></li>
<li>Tim Yates mixes the <a href="https://gist.github.com/timyates/99725e049fa9413f7e09">Document Builder and his ASCII image generator</a></li>
<li>Fernando Redondo shares the code of his Greach <a href="https://twitter.com/pronoide_fer/status/586825551907794944">Spock workshop</a></li>
<li>Bruno Borges uses Ratp<a href="https://github.com/brunoborges/nashorn/blob/master/maven/samples/ratpack/ratpack.js">ack in JavaScript with the Nashorn Maven executor</a></li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>Object Computing is <a href="https://twitter.com/NathanTippy/status/586887533398601728">looking for Groovy developers in Saint-Louis</a>, USA</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/gr8conf/status/587841371266015232">GR8Conf Europe early bird tickets ending on Wednesday</a>, be fast!</li>
<li>A new <a href="https://twitter.com/glaforge/status/587572281511141376">Grails event in Azerbaijan</a></li>
<li>Tomas Lin collected the links to the <a href="https://fbflex.wordpress.com/2015/04/12/collection-of-links-for-greach-2015-from-twitter/">slides and code of all the Greach 2015 conference presentations</a></li>
<li>Browse the <a href="https://twitter.com/greachconf/status/587251262615334913">Greach 2015 conference pictures</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #65</title><link>https://glaforge.dev/posts/2015/04/07/groovy-weekly-65/</link><pubDate>Tue, 07 Apr 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/04/07/groovy-weekly-65/</guid><description>&lt;p>I hope you all had a Groovy Easter, with baskets full of eggs and chocolate!&lt;/p>
&lt;p>Little update on the Groovy infrastructure, as the project moves into incubation at Apache, the &lt;a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved&lt;/a>, and don’t forget that the &lt;a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving&lt;/a>.&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/arasthel92/status/584746749245423617">SwissKnife 1.2.3&lt;/a> released, the Swiss knife for for Android development with Groovy&lt;/li>
&lt;li>&lt;a href="http://ratpack.io/versions/0.9.15">Ratpack 0.9.15&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/MGrzejszczak/status/583390682938798080">Spock Subjects Collaborators Extension 1.0.3&lt;/a> released by Marcin Grzejszczak&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grailsframework/status/583557647623913472">Grails 3.0.1&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/pickypg/status/585108230373830656">ElasticSearch Groovy client v1.5.0&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://jaxenter.com/grails-3-0-built-upon-spring-boot-and-gradle-116071.html">Grails 3.0 built upon Spring Boot and Gradle&lt;/a> on JaxEnter&lt;/li>
&lt;li>Using &lt;a href="http://www.objectpartners.com/2015/03/31/using-android-product-flavors-to-build-full-and-demo-version-of-the-app/">Android product flavors to build full and demo version of an application&lt;/a>, by Manij Shrestha&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>The &lt;a href="https://issues.apache.org/jira/browse/GROOVY/?selectedTab=com.atlassian.jira.jira-projects-plugin:summary-panel">Groovy JIRA has now moved to Apache&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/Gradleware/status/583310059108712448">Gradle has a brand new forum&lt;/a> powered by Discourse&lt;/li>
&lt;li>Tim Fox shares a glimpse of the &lt;a href="http://vert-x3.github.io/">upcoming new Vert.x website&lt;/a>&lt;/li>
&lt;li>Jacob Aae Mikkelsen &lt;a href="http://grydeske.net/news/show/90">Grails Diary&lt;/a> week 14&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>SpringOne2GX 2014
&lt;ul>
&lt;li>&lt;a href="http://www.infoq.com/presentations/ratpack-grails-3">Ratpack and Grails 3&lt;/a> by Lari Hotari&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/presentations/grails-api-2014">Building awesome APIs with Grails&lt;/a> by Chris Latimer&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/presentations/gradle-android">Gradle for Android&lt;/a> by Ken Kousen&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=CKkovWazbJM">Introduction to Grails 3.0&lt;/a> by Dan Vega&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=PlMyRGIJNa0">Creating and running a Grails 3 application&lt;/a> by Dan Vega&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/582180967164563456">Creating iOS applications with Groovy&lt;/a> and GrooScript, using ReactJS native&lt;/li>
&lt;li>April Fools from Apache Groovy mentor says &lt;a href="https://twitter.com/bdelacretaz/status/583192854757007360">Groovy could graduate on April 1st&lt;/a> already&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/583384389201080321">Spring Boot 1.1.12&lt;/a> available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/583384477612797953">Spring Boot 1.2.3&lt;/a> available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/583555987854008320">Grails 3.0.1&lt;/a> available on GVM&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-list-posts">Mailing-list posts&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://groovy.329449.n5.nabble.com/ANN-JIRA-moved-to-Apache-td5723366.html">Groovy&amp;rsquo;s JIRA moved to Apache&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>Peter Ledbrook shares early &lt;a href="https://twitter.com/pledbrook/status/583291238067077120">draft chapters of his Practical Groovy&lt;/a> self-published book&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/gr8confus/status/585089220848517121">GR8Conf US 2015 registration is open&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>I hope you all had a Groovy Easter, with baskets full of eggs and chocolate!</p>
<p>Little update on the Groovy infrastructure, as the project moves into incubation at Apache, the <a href="https://issues.apache.org/jira/browse/GROOVY/">JIRA issue tracker has moved</a>, and don’t forget that the <a href="http://www.groovy-lang.org/mailing-lists.html">Groovy mailing-lists are also moving</a>.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/arasthel92/status/584746749245423617">SwissKnife 1.2.3</a> released, the Swiss knife for for Android development with Groovy</li>
<li><a href="http://ratpack.io/versions/0.9.15">Ratpack 0.9.15</a> released</li>
<li><a href="https://twitter.com/MGrzejszczak/status/583390682938798080">Spock Subjects Collaborators Extension 1.0.3</a> released by Marcin Grzejszczak</li>
<li><a href="https://twitter.com/grailsframework/status/583557647623913472">Grails 3.0.1</a> released</li>
<li><a href="https://twitter.com/pickypg/status/585108230373830656">ElasticSearch Groovy client v1.5.0</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://jaxenter.com/grails-3-0-built-upon-spring-boot-and-gradle-116071.html">Grails 3.0 built upon Spring Boot and Gradle</a> on JaxEnter</li>
<li>Using <a href="http://www.objectpartners.com/2015/03/31/using-android-product-flavors-to-build-full-and-demo-version-of-the-app/">Android product flavors to build full and demo version of an application</a>, by Manij Shrestha</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="https://issues.apache.org/jira/browse/GROOVY/?selectedTab=com.atlassian.jira.jira-projects-plugin:summary-panel">Groovy JIRA has now moved to Apache</a></li>
<li><a href="https://twitter.com/Gradleware/status/583310059108712448">Gradle has a brand new forum</a> powered by Discourse</li>
<li>Tim Fox shares a glimpse of the <a href="http://vert-x3.github.io/">upcoming new Vert.x website</a></li>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/90">Grails Diary</a> week 14</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>SpringOne2GX 2014
<ul>
<li><a href="http://www.infoq.com/presentations/ratpack-grails-3">Ratpack and Grails 3</a> by Lari Hotari</li>
<li><a href="http://www.infoq.com/presentations/grails-api-2014">Building awesome APIs with Grails</a> by Chris Latimer</li>
<li><a href="http://www.infoq.com/presentations/gradle-android">Gradle for Android</a> by Ken Kousen</li>
<li><a href="https://www.youtube.com/watch?v=CKkovWazbJM">Introduction to Grails 3.0</a> by Dan Vega</li>
<li><a href="https://www.youtube.com/watch?v=PlMyRGIJNa0">Creating and running a Grails 3 application</a> by Dan Vega</li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/582180967164563456">Creating iOS applications with Groovy</a> and GrooScript, using ReactJS native</li>
<li>April Fools from Apache Groovy mentor says <a href="https://twitter.com/bdelacretaz/status/583192854757007360">Groovy could graduate on April 1st</a> already</li>
<li><a href="https://twitter.com/gvmtool/status/583384389201080321">Spring Boot 1.1.12</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/583384477612797953">Spring Boot 1.2.3</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/583555987854008320">Grails 3.0.1</a> available on GVM</li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-JIRA-moved-to-Apache-td5723366.html">Groovy&rsquo;s JIRA moved to Apache</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Peter Ledbrook shares early <a href="https://twitter.com/pledbrook/status/583291238067077120">draft chapters of his Practical Groovy</a> self-published book</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/gr8confus/status/585089220848517121">GR8Conf US 2015 registration is open</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #64</title><link>https://glaforge.dev/posts/2015/03/31/groovy-weekly-64/</link><pubDate>Tue, 31 Mar 2015 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2015/03/31/groovy-weekly-64/</guid><description>&lt;p>Today marks the &lt;a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">end of the sponsorship of the Groovy and Grails projects by Pivotal&lt;/a>. The projects are now turning a new page in their lives. For Groovy, the project is going to wear some feathers, with &lt;a href="http://www.programmableweb.com/news/groovy-project-joins-apache-software-foundation/2015/03/27">joining the Apache Software Foundation&lt;/a>. And Grails is &lt;a href="http://grails.io/post/115110650393/grails-3-0-released-and-the-road-ahead">releasing its major 3.0 version&lt;/a>.&lt;/p>
&lt;p>With Groovy’s move to Apache, it’s important to notice that the Groovy mailing-lists are moving. You &lt;a href="http://groovy.329449.n5.nabble.com/IMPORTANT-New-mailing-lists-and-JIRA-migration-td5723329.html">must subscribe to the new mailing-lists at Apache&lt;/a>, as we won’t force-subscribe people, so you’ll have to opt-in to these new lists.&lt;/p></description><content:encoded>
<![CDATA[<p>Today marks the <a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">end of the sponsorship of the Groovy and Grails projects by Pivotal</a>. The projects are now turning a new page in their lives. For Groovy, the project is going to wear some feathers, with <a href="http://www.programmableweb.com/news/groovy-project-joins-apache-software-foundation/2015/03/27">joining the Apache Software Foundation</a>. And Grails is <a href="http://grails.io/post/115110650393/grails-3-0-released-and-the-road-ahead">releasing its major 3.0 version</a>.</p>
<p>With Groovy’s move to Apache, it’s important to notice that the Groovy mailing-lists are moving. You <a href="http://groovy.329449.n5.nabble.com/IMPORTANT-New-mailing-lists-and-JIRA-migration-td5723329.html">must subscribe to the new mailing-lists at Apache</a>, as we won’t force-subscribe people, so you’ll have to opt-in to these new lists.</p>
<p>Last but not least, Groovy has been submitted for nomination for the JAX 2015 innovation award, so we encourage you to <a href="http://jaxenter.com/jax-awards-2015/submit-your-vote">vote for Groovy for the JAX innovation award</a>!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://github.com/grails/grails-core/releases/tag/v3.0.0">Grails 3.0</a> released</li>
<li><a href="https://twitter.com/grailsframework/status/580610565808197632">Grails 2.4.5 and 2.5.0</a> are released</li>
<li><a href="https://twitter.com/grailsframework/status/581094391521132546">Grails 3.0 RC-3</a> released, the last RC before the final release</li>
<li>Al Baker releases <a href="https://twitter.com/AlBaker_Dev/status/581168099614937088">Stardog Groovy 3.0</a></li>
<li><a href="https://twitter.com/NebulaPlugins/status/581203594021584897">Gradle Nebula-test plugin releases v2.2.1</a> with an updated Spock 1.0</li>
<li><a href="https://twitter.com/craigburke1/status/582575048764055552">Document Builder 0.3.0</a> released by Craig Burke with header / footer support</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://grails.io/post/115110650393/grails-3-0-released-and-the-road-ahead">Grails 3.0 released and the road ahead</a> by Graeme Rocher</li>
<li>ProgrammableWeb covers <a href="http://www.programmableweb.com/news/groovy-project-joins-apache-software-foundation/2015/03/27">Groovy project joining the Apache Software Foundation</a></li>
<li>Cédric Champeau dives into the details of an <a href="http://melix.github.io/blog/2015/03/sandboxing.html">improved sandboxing approach for Groovy scripts</a></li>
<li>Write your <a href="https://impetus-games.com/blog/libGDX-the-Groovy-Way">games in Groovy with libGDX</a></li>
<li>Marco Vermeulen details the <a href="http://www.wiredforcode.com/blog/2015/03/26/the-gvm-vendor-api/">GVM vendor API</a></li>
<li>Pavel Dudka&rsquo;s first Gradle <a href="http://trickyandroid.com/gradle-tip-1-tasks/">tip on Gradle tasks</a></li>
<li><a href="https://gradle.org/gradle-2-4-the-fastest-yet/">Gradle 2.4, the fastest yet</a>, says Luke Daley</li>
<li>Hans Dockter gives the <a href="https://gradle.org/gradle-team-perspective-on-bazel">Gradle team&rsquo;s perspective on Google&rsquo;s Bazel</a> build solution</li>
<li><a href="https://gradle.org/why/return-on-investment/">How to pitch Gradle to your boss</a>? Here&rsquo;s a recipe on Gradle&rsquo;s website</li>
<li>MrHaki&rsquo;s Groovy Goodness: <a href="http://mrhaki.blogspot.fr/2015/03/groovy-goodness-new-methods-to-sort-and.html">New methods to sort and remove duplicates from collection</a></li>
<li>Peter Ledbrook covers the infamous <a href="http://blog.cacoethes.co.uk/gradle/comments-on-recent-gradle-criticisms">Gradle halting problem</a></li>
<li>Ratpack 0.9.15 will feature <a href="https://twitter.com/ratpackweb/status/582642475388444672">non blocking health checks</a></li>
<li>Integration vs functional testing: how to <a href="http://aruizca.com/integrated-vs-functional-testing-how-to-test-rest-apis-in-grails-using-spock/">test REST APIs in Grails using Spock</a>, by Angel Ruiz</li>
<li><a href="http://devsoap.com/#!/Writing-AngularJS-applications-in-Groovy">Writing AngularJS applications in Groovy</a> by John Ahlroos</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="https://twitter.com/grailsframework/status/582466373894438912">Grails is moving away from JIRA to Github issues</a></li>
<li>The <a href="https://twitter.com/kenkousen/status/580745262148280320">Groovy Podcast now has a Twitter feed</a>, announces Ken Kousen</li>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/89">Grails Diary</a> week 13</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>SpringOne2GX 2014
<ul>
<li><a href="http://www.infoq.com/presentations/advanced-gorm">Advanced GORM, beyond relational</a>, by Graeme Rocher</li>
<li><a href="http://www.infoq.com/presentations/grails-perf-tuning">Performance tuning Grails applications</a> by Lari Hotari</li>
<li><a href="http://www.infoq.com/presentations/groovy-metaprogramming">Runtime meta-programming with Groovy</a> by Jeff Brown</li>
<li><a href="http://www.infoq.com/presentations/groovy-spock-gradle">Groovy mobile automation</a> by Bobby Warner</li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau tweets about the <a href="https://twitter.com/CedricChampeau/status/582656729600622592">new mailing-lists for the Groovy project at Apache</a>, encouraging all users to move to the new lists</li>
<li>The <a href="https://twitter.com/cedricchampeau/status/582908247692181504">new Groovy documentation is already 630 pages long in PDF</a> form, with all code snippets automatically tested, notes Cédric Champeau</li>
<li>Rob Winch and Sam Brannen are experiencing important <a href="https://twitter.com/sam_brannen/status/581884182156197888">decreased build times thanks to Gradle 2.4</a> build time improvements</li>
<li>Eugene Kamenev announces the use of Gitter for <a href="https://twitter.com/eugenekamenev/status/582548963661729792">chatting about SwissKnife and Groovy Android</a> support</li>
<li>Cédric Champeau&rsquo;s <a href="https://twitter.com/CedricChampeau/status/581129087126609920">Bytecode AST transformation supports debugging</a></li>
<li>Dan Woods notices the <a href="https://twitter.com/danveloper/status/581553702525214720">NIO support from Groovy 2.3</a></li>
<li>Dan Woods is sad to learn that the <a href="https://twitter.com/danveloper/status/581765073258987520">Groovy Grails Tool Suite is discontinued</a>, following up Pivotal&rsquo;s end of funding of the Groovy and Grails projects</li>
<li>Dierk König notes that <a href="https://twitter.com/mittie/status/581768305771450368">Groovy&rsquo;s safe navigation operator is like the Maybe monad</a>, and GPath is like a list monad</li>
<li><a href="https://twitter.com/gvmtool/status/580608623719686144">Grails 2.4.5</a> is available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/580608731085447168">Grails 2.5.0</a> is available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/581097862899113986">Grails 3.0 RC-3</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/582850247929532416">Grails 3.0</a> is available on GVM</li>
<li>Craig Burke is moving his <a href="https://twitter.com/craigburke1/status/580732939371171840">document builder to OOXML</a></li>
<li>Craig Burke managed to get <a href="https://twitter.com/craigburke1/status/580788527341924352">headers working for his document builder</a></li>
<li>Al Baker says <a href="https://twitter.com/AlBaker_Dev/status/581169267619602433">Stardog Groovy 3.0 includes simplified reasoning configuration</a></li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>With its move to the Apache foundation, Groovy is moving its mailing-lists, and <a href="http://groovy.329449.n5.nabble.com/IMPORTANT-New-mailing-lists-and-JIRA-migration-td5723329.html">subscribers of the old lists should subscribe to the new ones</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li><a href="https://twitter.com/groovypodcast/status/581107353459658752">Groovy podcast episode 9</a> is available</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Tim Yates has fun with some <a href="https://gist.github.com/timyates/7234c83d9e7df39e5a09">ASCII based image generation</a> script</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The latest <a href="https://twitter.com/gr8conf/status/580603331707559936">newsletter about GR8Conf Europe</a> 2015</li>
<li>If you&rsquo;re wondering about <a href="https://twitter.com/gr8conf/status/580769221023399936">GR8Conf Europe, check its YouTube channel</a> to figure out!</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #63</title><link>https://glaforge.dev/posts/2015/03/24/groovy-weekly-63/</link><pubDate>Tue, 24 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/24/groovy-weekly-63/</guid><description>&lt;p>The big news today is that Groovy has now &lt;a href="https://glaforge.dev/posts/2015/03/23/groovy-2-4-3-out-and-entering-apache-s-incubator/">passed the vote for entering the Apache Software Foundation incubator&lt;/a>! Thus, in the coming weeks and months, while going through the incubation process, you can expect some changes in terms of infrastructure (moving sources, issue tracker, mailng-lists, etc) to be taking place, but don’t worry, we’ll keep you informed along the way.&lt;/p>
&lt;p>This past week, we’ve had some new releases of Groovy, as well as the second release candidate for Grails 3! Grails 3 is really really close now, get ready, it’s gonna rock the house!&lt;/p></description><content:encoded>
<![CDATA[<p>The big news today is that Groovy has now <a href="https://glaforge.dev/posts/2015/03/23/groovy-2-4-3-out-and-entering-apache-s-incubator/">passed the vote for entering the Apache Software Foundation incubator</a>! Thus, in the coming weeks and months, while going through the incubation process, you can expect some changes in terms of infrastructure (moving sources, issue tracker, mailng-lists, etc) to be taking place, but don’t worry, we’ll keep you informed along the way.</p>
<p>This past week, we’ve had some new releases of Groovy, as well as the second release candidate for Grails 3! Grails 3 is really really close now, get ready, it’s gonna rock the house!</p>
<p>Alongside the Groovy releases, <a href="https://glaforge.dev/posts/2015/03/19/groovy-2-4-2-and-2-3-11-released/">big efforts have been made with the new Groovy documentation</a>. Be sure to check it out, and to help us further improve that documentation</p>
<p>Last but not least, Groovy has been submitted for nomination for the JAX 2015 innovation award, so we encourage you to <a href="http://jaxenter.com/jax-awards-2015/submit-your-vote">vote for Groovy for the JAX innovation award</a>!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grailsframework/status/578127217198174208">Grails 3.0 rc-2</a> released</li>
<li><a href="https://glaforge.dev/posts/2015/03/19/groovy-2-4-2-and-2-3-11-released/">Groovy 2.4.2 and 2.3.11</a> released along with tons of new documentation</li>
<li><a href="http://groovy-lang.org/changelogs/changelog-2.4.3.html">Groovy 2.4.3</a> released</li>
<li><a href="https://twitter.com/CedricChampeau/status/580017871620874241">Gradle Android plugin for Groovy 0.3.6</a> released, working with the 1.1.0 version of the Android plugin</li>
<li><a href="https://twitter.com/CedricChampeau/status/579727386452819969">japicmp plugin for Gradle 0.1.1</a> released</li>
<li><a href="https://twitter.com/SonarSource/status/579950945263620096">SonarQube Groovy 1.1</a> released with JaCoCo coverage and latest CodeNarc 0.23</li>
<li><a href="https://www.jetbrains.com/idea/whatsnew/">IntelliJ IDEA 14.1</a> released with improved Groovy and Gradle support</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Guillaume Laforge announces the <a href="https://glaforge.dev/posts/2015/03/23/groovy-2-4-3-out-and-entering-apache-s-incubator/">release of Groovy 2.4.3 and that the vote for accepting Groovy in the Apache incubator passed</a></li>
<li>Schalk Cronjé shares his thoughts on <a href="http://delivervalue.blogspot.co.uk/2015/03/so-what-about-this-halting-problem-in.html">Gradle&rsquo;s halting problem</a></li>
<li>MrHaki&rsquo;s Groovy Goodness
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/03/groovy-goodness-use-constructor-as.html">Use constructor as method pointer</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/03/groovy-goodness-combine-elements.html">Combine elements iterable with index</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/03/groovy-goodness-swapping-elements-in.html">Swapping elements in a collection</a></li>
</ul>
</li>
<li><a href="https://solidsoft.wordpress.com/2015/03/24/automatic-promotion-of-artifacts-to-maven-central-from-gradle/">Automatic release / promotion of artifacts to Maven Central from Gradle</a> (with Gradle Nexus Staging Plugin)</li>
<li>Set up easy <a href="http://blog.comsysto.com/2015/02/20/cross-language-benchmarking-made-easy/">cross-language benchmarking using Gradle</a> by Ben Steinert</li>
<li><a href="http://wiebe-elsinga.com/blog/archive-addition-android-artifacts-with-gradle/">Archive addition Android artifacts with Gradle</a> by W.Elsinga</li>
<li><a href="https://tedvinke.wordpress.com/2015/03/15/basic-groovy-and-grails-code-review-guidelines/">Basic Groovy and Grails code review guidelines</a> by Ted Vinke</li>
<li>Using delegation for <a href="http://helpingtoomuch.blogspot.fr/2015/03/using-delegation-for-audit-columns-in.html">audit columns in Groovy on Grails</a></li>
<li>Dustin Marx on <a href="http://java.dzone.com/articles/excellent-groovy-intends-join">Groovy joining the Apache foundation</a></li>
<li><a href="https://twitter.com/bgoetzmann/status/577958849551822848">Control a presentation remotely with a Vert.x module written in Groovy</a> by Bertrand Goetzman</li>
<li>Why <a href="http://www.techgig.com/readnews.php?category=Technology%2F+Skill+News&amp;tgnews_link=http%3A%2F%2Ffeeds.dzone.com%2F~r%2Fdzone%2Ffrontpage%2F~3%2FYLKvHSdp1X0%2Fwhy_developing_in_groovygrails_is_faster_than_in.html&amp;tg_type=rss&amp;tgnews_id=54456">developing in Groovy and Grails is faster than in Java and Spring</a> asks Luigi Candita</li>
<li>Jorina Freya Gerken introduces a new series on <a href="http://eclipsesource.com/blogs/2015/03/20/spock-by-example-introducing-the-series/">Spock by example</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>You can <a href="http://jaxenter.com/jax-awards-2015/submit-your-vote">vote for Groovy for the JAX 2015 innovation award</a></li>
<li>The <a href="http://www.gradle.org/">Gradle website has been redesigned</a></li>
<li>Jacob Aae Mikkelsen <a href="https://twitter.com/JacobAae/status/580150710886645760">Grails Diary</a> week 12</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Iván López published his code and slides on <a href="https://twitter.com/ilopmar/status/578121164632936450">Grails and the realtime world</a></li>
<li>Video of the <a href="https://twitter.com/andrewreitz_/status/580337018493775873">Getting Groovy on Android</a> presentation by Andrew Reitz</li>
<li>Peter Ledbrook gave a tutorial on <a href="https://www.parleys.com/talk/groovy-java-developers">Groovy for Java developers</a> at Devoxx UK 2014</li>
<li>SpringOne2GX 2014
<ul>
<li>
<p><a href="http://www.infoq.com/presentations/groovy-ast">Groovy AST transformations</a> by Paul King</p>
</li>
<li>
<p><a href="http://www.infoq.com/presentations/groovy-distributed-enterprise">Distributed Platform Development with Groovy</a> by Dan Woods</p>
</li>
<li>
<p><a href="http://www.infoq.com/presentations/groovy-tips-tricks">Advanced Groovy Tips and Tricks</a> by Ken Kousen</p>
</li>
<li>
<p><a href="http://www.infoq.com/presentations/groovy-gorm">GORM Inside and out</a> by Jeff Scott Brown</p>
</li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Bertrand Delacretaz, mentor of the Groovy project, announces the <a href="https://twitter.com/bdelacretaz/status/580057703202086912">vote passed to accept Groovy in the Apache Software Foundation incubator</a></li>
<li>Cédric Champeau updated his <a href="https://twitter.com/CedricChampeau/status/580479826563362817">@Bytecode AST transformation</a>, but he warns about using it for educational purpose only</li>
<li>Baruch Sadogursky claims that <a href="https://twitter.com/jbaruch/status/580269909520752640">Maven&rsquo;s biggest problem is not XML so polyglotism won&rsquo;t help it</a></li>
<li><a href="https://twitter.com/gvmtool/status/580071538805862400">Groovy 2.4.3</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/579688895304085504">Groovy 2.4.2</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/579688160973713408">Groovy 2.3.11</a> available on GVM</li>
<li>Creating <a href="https://twitter.com/PSJedox/status/578671464074752003/photo/1">CUDA code with Groovy scripts</a></li>
<li>Bavo Bruylandt says that the <a href="https://twitter.com/bavobbr/status/578807970886209537">@ClosureParams and @DelegatesTo annotations greatly help IDE integration for Groovy DSLs</a></li>
<li>Ben McGuire noted that most NFJS attendees were using Groovy in some capacity, showing an <a href="https://twitter.com/ben_t_mcguire/status/579399082029944832">ongoing growth of the Groovy community</a></li>
<li>Oliver Gierke likes the <a href="https://twitter.com/olivergierke/status/577903566070902785">Groovy based poms with Maven 3.3.1</a></li>
<li>Oliver Gierke ported the Spring HATEOAS <a href="https://twitter.com/olivergierke/status/578521532349157376">pom.xml to Groovy with Maven&rsquo;s polyglot</a> support</li>
<li><a href="https://twitter.com/grailsframework/status/577843537284325378">Third party plugins compatible with Grails 3</a> are becoming available</li>
<li>A <a href="https://twitter.com/FlywayDb/status/577872090998575108">Flyway Gradle plugin</a> appears into the Gradle plugin portal</li>
<li><a href="https://twitter.com/gvmtool/status/578129119361306624">Grails 3.0 rc-2</a> available on GVM</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/ManningMEAP/status/580416001814204417">Groovy in Action, 2nd edition</a>, in the finish line!</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/gr8conf/status/580435505852071936">Tickets for GR8Conf Europe</a> now on sale!</li>
<li><a href="https://twitter.com/gr8conf/status/577996465248718848">GR8Conf Europe 2015 is available on Lanyrd</a>, you can mark you&rsquo;re attending</li>
<li><a href="https://twitter.com/greachconf/status/579609854320381952">Two Java Champions will be speaking at the Greach</a> conference, with Andrés Almiray and Trisha Gee</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.4.3 out and entering Apache's incubator</title><link>https://glaforge.dev/posts/2015/03/23/groovy-2-4-3-out-and-entering-apache-s-incubator/</link><pubDate>Mon, 23 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/23/groovy-2-4-3-out-and-entering-apache-s-incubator/</guid><description>&lt;p>The Groovy team is happy to announce another 2.4 release, with Groovy 2.4.3.&lt;/p>
&lt;p>We noticed some regressions that needed our attention, and we decided to release rapidly after our recent 2.4.2 release of last week. Please upgrade to 2.4.3 if you&amp;rsquo;re on the 2.4.x line.&lt;/p>
&lt;p>You can read about the tickets resolved in our changelog: &lt;a href="http://groovy-lang.org/changelogs/changelog-2.4.3.html">http://groovy-lang.org/changelogs/changelog-2.4.3.html&lt;/a>&lt;/p>
&lt;p>And you can download the &lt;a href="http://www.groovy-lang.org/download.html">Groovy distribution&lt;/a> as usual on the new Groovy website.&lt;/p>
&lt;p>I&amp;rsquo;d also like to announce that Groovy &lt;a href="http://markmail.org/message/uaickpdsffd4gnzu">passed the vote to enter the Apache Software Foundation Incubator&lt;/a>! In the coming weeks, we&amp;rsquo;ll be through the incubation process, likely for a few months, where we&amp;rsquo;ll be moving things around (sources, JIRA, mailing-lists, etc), so you can expect a bit of change, but we&amp;rsquo;ll keep you posted on how we progress along.&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy team is happy to announce another 2.4 release, with Groovy 2.4.3.</p>
<p>We noticed some regressions that needed our attention, and we decided to release rapidly after our recent 2.4.2 release of last week. Please upgrade to 2.4.3 if you&rsquo;re on the 2.4.x line.</p>
<p>You can read about the tickets resolved in our changelog: <a href="http://groovy-lang.org/changelogs/changelog-2.4.3.html">http://groovy-lang.org/changelogs/changelog-2.4.3.html</a></p>
<p>And you can download the <a href="http://www.groovy-lang.org/download.html">Groovy distribution</a> as usual on the new Groovy website.</p>
<p>I&rsquo;d also like to announce that Groovy <a href="http://markmail.org/message/uaickpdsffd4gnzu">passed the vote to enter the Apache Software Foundation Incubator</a>! In the coming weeks, we&rsquo;ll be through the incubation process, likely for a few months, where we&rsquo;ll be moving things around (sources, JIRA, mailing-lists, etc), so you can expect a bit of change, but we&rsquo;ll keep you posted on how we progress along.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.4.2 and 2.3.11 released</title><link>https://glaforge.dev/posts/2015/03/19/groovy-2-4-2-and-2-3-11-released/</link><pubDate>Thu, 19 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/19/groovy-2-4-2-and-2-3-11-released/</guid><description>&lt;p>The Groovy development team is happy to announce the bug fix releases of Groovy 2.4.2 and Groovy 2.3.11.&lt;/p>
&lt;p>For the 2.4.2 release, some &lt;strong>key performance improvements have found their way in static compilation mode&lt;/strong>, so you might be happy to see your programs snappier!&lt;/p>
&lt;p>Along with those two releases, you&amp;rsquo;ll notice &lt;strong>lots more documentation has been written&lt;/strong> on the new Groovy website, as well as important updates to existing topics. You might want to have a look at the following new or improved sections:&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy development team is happy to announce the bug fix releases of Groovy 2.4.2 and Groovy 2.3.11.</p>
<p>For the 2.4.2 release, some <strong>key performance improvements have found their way in static compilation mode</strong>, so you might be happy to see your programs snappier!</p>
<p>Along with those two releases, you&rsquo;ll notice <strong>lots more documentation has been written</strong> on the new Groovy website, as well as important updates to existing topics. You might want to have a look at the following new or improved sections:</p>
<ul>
<li>
<p><a href="http://docs.groovy-lang.org/latest/html/documentation/core-domain-specific-languages.html">Domain-Specific Languages</a></p>
</li>
<li>
<p>core language</p>
<ul>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_scripts_versus_classes">scripts vs classes</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_fields_and_properties">fields and properties</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_interfaces">interfaces</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_labeled_statements">labeled statements</a></li>
<li><a href="http://docs.groovy-lang.org/latest/html/documentation/core-traits.html">traits</a></li>
</ul>
</li>
<li>
<p>annotations</p>
<ul>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_meta_annotations">meta-annotations</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_closure_annotation_parameters">closure annotation parameters</a></li>
</ul>
</li>
<li>
<p>AST</p>
<ul>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_testing_ast_transformations">testing AST transformations</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_ast_api_guide">AST API guide</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#transforms-global">global AST transformations</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#transforms-local">local AST transformations</a></li>
</ul>
</li>
<li>
<p><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_power_assertions">power assertions</a></p>
</li>
<li>
<p><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_script_base_classes">base script classes</a></p>
</li>
<li>
<p><a href="http://docs.groovy-lang.org/latest/html/documentation/type-checking-extensions.html">type checking extensions</a></p>
</li>
<li>
<p><a href="http://docs.groovy-lang.org/latest/html/documentation/template-engines.html">template engines</a></p>
</li>
<li>
<p><a href="http://docs.groovy-lang.org/latest/html/documentation/markup-template-engine.html">markup template engine</a></p>
</li>
<li>
<p>builders</p>
<ul>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_antbuilder">Ant builder</a></li>
<li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_objectgraphbuilder">object graph builder</a></li>
</ul>
</li>
<li>
<p>guides</p>
<ul>
<li><a href="http://docs.groovy-lang.org/latest/html/documentation/servlet-userguide.html">servlet user guide</a></li>
<li><a href="http://docs.groovy-lang.org/latest/html/documentation/working-with-collections.html">working with collections</a></li>
<li><a href="http://docs.groovy-lang.org/latest/html/documentation/working-with-io.html">working with I/O</a></li>
<li><a href="http://docs.groovy-lang.org/latest/html/documentation/core-testing-guide.html">testing guide</a></li>
</ul>
</li>
</ul>
<p>Note that it might be the last releases under the Codehaus era, as Groovy is <strong>currently being voted for incubation at the Apache Software Foundation</strong>. The vote is ongoing right now, as I write those lines, so we should know pretty soon what&rsquo;s the outcome, and if Groovy is accepted or not! So far&hellip; only positive votes!</p>
<p>You can:</p>
<ul>
<li><strong>download</strong> those latest Groovy releases in our <a href="http://www.groovy-lang.org/download.html">download area</a></li>
<li>read the <strong>release notes</strong> for</li>
<li><a href="http://www.groovy-lang.org/changelogs/changelog-2.4.2.html">Groovy 2.4.2</a></li>
<li><a href="http://www.groovy-lang.org/changelogs/changelog-2.3.11.html">Groovy 2.3.11</a></li>
</ul>
<p>Thanks a lot to all who contributed to those releases, and thanks for your support of Groovy and your positive feedback on our move to Apache!</p>
<p>Keep on groovy&rsquo;ing!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #62</title><link>https://glaforge.dev/posts/2015/03/17/groovy-weekly-62/</link><pubDate>Tue, 17 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/17/groovy-weekly-62/</guid><description>&lt;p>Following-up last week’s &lt;a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">decision of the Groovy team to join the Apache Software Foundation&lt;/a>, this week, a &lt;a href="https://wiki.apache.org/incubator/GroovyProposal">proposal&lt;/a> has been made and submitted, and Apache has &lt;a href="https://blogs.apache.org/foundation/entry/groovy_submitted_to_become_a">announced it on its blog&lt;/a>. So the process is now really officially started and on its track!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/grailsframework/status/575995370376949760">Grails 3.0 RC-1&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/575689514317975552">GVM 2.4.0&lt;/a> released with broadcast and offline checks optimizations&lt;/li>
&lt;li>&lt;a href="https://spring.io/blog/2015/03/11/spring-tool-suite-and-groovy-grails-tool-suite-3-6-4-released">Groovy Grails Toolsuite 3.6.4&lt;/a> released with Groovy 2.4 support&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>The Groovy team and Apache members collaborated on a &lt;a href="https://wiki.apache.org/incubator/GroovyProposal">proposal to join the Apache Software Foundation&lt;/a>&lt;/li>
&lt;li>Matt Raible &lt;a href="http://www.infoq.com/news/2015/03/groovy-moving-to-apache">interviewed Guillaume Laforge about Groovy moving to Apache&lt;/a>&lt;/li>
&lt;li>Darryl Taft from &lt;a href="http://www.eweek.com/developer/groovy-language-proposed-as-apache-incubator-project.html">eWeek is covering the proposal of Groovy to join the Apache&lt;/a> incubator&lt;/li>
&lt;li>Paul Krill writes on CIO &amp;ldquo;&lt;a href="http://www.cio.com.au/article/570225/dumped-by-pivotal-groovy-moves-apache/">dumped by Pivotal, Groovy moves to Apache&lt;/a>&amp;rdquo;&lt;/li>
&lt;li>Kay Ewbank on &lt;a href="http://www.i-programmer.info/news/136-open-source/8378-groovy-joins-apache.html">Groovy joining the Apache Software Foundation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/Shmoolki/status/576818810587611136">Groovy hands-on&lt;/a>, in French&lt;/li>
&lt;li>A new &lt;a href="http://new.griffon-framework.org/tutorials/4_javafx_views.html">Griffon / JavaFX tutorial&lt;/a> is available&lt;/li>
&lt;li>A &lt;a href="http://wiki.apidesign.org/wiki/Gradle">flamewar against Gradle&lt;/a> which is said to suffer from the halting problem&lt;/li>
&lt;li>Allegro Tech is sharing its &lt;a href="http://allegrotech.io/Adopting-Gradle-at-allegro-pl-a-success-story.html">success story adopting Gradle&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>The Apache Software Foundation announces &lt;a href="https://blogs.apache.org/foundation/entry/groovy_submitted_to_become_a">Groovy&amp;rsquo;s submission to become an official Apache project&lt;/a>&lt;/li>
&lt;li>At the Adobe Summit, &lt;a href="https://github.com/TWCable/cq-gradle-plugin">Time Warner Cable announced some of its Gradle plugins&lt;/a> to work with Adobe tools&lt;/li>
&lt;li>A very interesting contribution from JetBrain&amp;rsquo;s Peter Gromov has been merged into Groovy&amp;rsquo;s master to &lt;a href="https://github.com/groovy/groovy-core/pull/552">speedup compilation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/andyjduncan/opencsv-iterator">OpenCSVIterator&lt;/a>: a useful library for processing flat text files in Groovy and Java&lt;/li>
&lt;li>GR8CRM, a suite of &lt;a href="http://gr8crm.github.io/">Grails plugins for Customer Relationship Management&lt;/a> by Göran Ehrsson&lt;/li>
&lt;li>A nice online &lt;a href="https://groovy-playground.appspot.com/">Groovy playground&lt;/a> by Tim Roes&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://groovy-the-superb-homeless.mally.pl/#/">Great introduction to Groovy&lt;/a> by Michał Mally at the Groovy user group in Poland&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/presentations/groovy-for-java">Groovy for Java developers&lt;/a>, by Peter Ledbrook, recorded during SpringOne2GX 2014&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/presentations/geb">Groovy browser automation with Geb&lt;/a>, by Colin Harrington, recorded at SpringOne2GX 2014&lt;/li>
&lt;li>A video introduction to &lt;a href="https://www.youtube.com/watch?v=cD7NPxuuXYY">Gradle for Android Studio&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau is annoyed when &lt;a href="https://twitter.com/CedricChampeau/status/576062386085433345">people reduce Groovy to just a &amp;ldquo;dynamic&amp;rdquo; language when it&amp;rsquo;s much more&lt;/a> than that&lt;/li>
&lt;li>Ken Kousen tasting a &lt;a href="https://twitter.com/kenkousen/status/576904271163232257">Groovy Smoothie&lt;/a>&lt;/li>
&lt;li>Matthew McCullough &lt;a href="https://twitter.com/matthewmccull/status/576062271924891649">Groovy JMeter plugin is moving to Github&lt;/a>, like the many Google Code projects following Google&amp;rsquo;s closing&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/576858409678540800">GrooScript plugin working for Grails 3 RC-1&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-list-posts">Mailing-list posts&lt;/h2>
&lt;ul>
&lt;li>The &lt;a href="http://mail-archives.apache.org/mod_mbox/incubator-general/201503.mbox/%3CCA+ULb+tHEc5N9vDvu526Jmb=te4F3yCwCWWJxxMC07EAz_35wQ@mail.gmail.com%3E">Groovy incubation proposal is being discussed on the Apache&lt;/a> mailing-lists&lt;/li>
&lt;/ul>
&lt;h2 id="podcasts">Podcasts&lt;/h2>
&lt;ul>
&lt;li>Ken Kousen published &lt;a href="https://twitter.com/kenkousen/status/576067688931454977">Groovy Podcast episode 8&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Using &lt;a href="https://gist.github.com/chiquitinxx/ef2d33ce9d1a810ba216">RxJs from GrooScript with Groovy&amp;rsquo;s @BaseScript&lt;/a> transformation&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>You can find Ken Kousen&amp;rsquo;s &lt;a href="https://twitter.com/manningbooks/status/577487522990714881">Making Java Groovy book code samples&lt;/a> on Manning&amp;rsquo;s website&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/springone2gx/status/576765439348318208">Registration for SpringOne2GX&lt;/a> 2015 is open&lt;/li>
&lt;li>Guillaume Laforge will be speaking about &lt;a href="https://twitter.com/jfrog/status/576052612144885762">Groovy&amp;rsquo;s continuous delivery at the SwampUp conference&lt;/a>&lt;/li>
&lt;li>At the Gradle Summit, &lt;a href="https://twitter.com/gradleware/status/577527498281472000">Netflix&amp;rsquo; build team will share lessons learned about Gradle plugins&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Following-up last week’s <a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">decision of the Groovy team to join the Apache Software Foundation</a>, this week, a <a href="https://wiki.apache.org/incubator/GroovyProposal">proposal</a> has been made and submitted, and Apache has <a href="https://blogs.apache.org/foundation/entry/groovy_submitted_to_become_a">announced it on its blog</a>. So the process is now really officially started and on its track!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grailsframework/status/575995370376949760">Grails 3.0 RC-1</a> released</li>
<li><a href="https://twitter.com/gvmtool/status/575689514317975552">GVM 2.4.0</a> released with broadcast and offline checks optimizations</li>
<li><a href="https://spring.io/blog/2015/03/11/spring-tool-suite-and-groovy-grails-tool-suite-3-6-4-released">Groovy Grails Toolsuite 3.6.4</a> released with Groovy 2.4 support</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>The Groovy team and Apache members collaborated on a <a href="https://wiki.apache.org/incubator/GroovyProposal">proposal to join the Apache Software Foundation</a></li>
<li>Matt Raible <a href="http://www.infoq.com/news/2015/03/groovy-moving-to-apache">interviewed Guillaume Laforge about Groovy moving to Apache</a></li>
<li>Darryl Taft from <a href="http://www.eweek.com/developer/groovy-language-proposed-as-apache-incubator-project.html">eWeek is covering the proposal of Groovy to join the Apache</a> incubator</li>
<li>Paul Krill writes on CIO &ldquo;<a href="http://www.cio.com.au/article/570225/dumped-by-pivotal-groovy-moves-apache/">dumped by Pivotal, Groovy moves to Apache</a>&rdquo;</li>
<li>Kay Ewbank on <a href="http://www.i-programmer.info/news/136-open-source/8378-groovy-joins-apache.html">Groovy joining the Apache Software Foundation</a></li>
<li><a href="https://twitter.com/Shmoolki/status/576818810587611136">Groovy hands-on</a>, in French</li>
<li>A new <a href="http://new.griffon-framework.org/tutorials/4_javafx_views.html">Griffon / JavaFX tutorial</a> is available</li>
<li>A <a href="http://wiki.apidesign.org/wiki/Gradle">flamewar against Gradle</a> which is said to suffer from the halting problem</li>
<li>Allegro Tech is sharing its <a href="http://allegrotech.io/Adopting-Gradle-at-allegro-pl-a-success-story.html">success story adopting Gradle</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The Apache Software Foundation announces <a href="https://blogs.apache.org/foundation/entry/groovy_submitted_to_become_a">Groovy&rsquo;s submission to become an official Apache project</a></li>
<li>At the Adobe Summit, <a href="https://github.com/TWCable/cq-gradle-plugin">Time Warner Cable announced some of its Gradle plugins</a> to work with Adobe tools</li>
<li>A very interesting contribution from JetBrain&rsquo;s Peter Gromov has been merged into Groovy&rsquo;s master to <a href="https://github.com/groovy/groovy-core/pull/552">speedup compilation</a></li>
<li><a href="https://github.com/andyjduncan/opencsv-iterator">OpenCSVIterator</a>: a useful library for processing flat text files in Groovy and Java</li>
<li>GR8CRM, a suite of <a href="http://gr8crm.github.io/">Grails plugins for Customer Relationship Management</a> by Göran Ehrsson</li>
<li>A nice online <a href="https://groovy-playground.appspot.com/">Groovy playground</a> by Tim Roes</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="http://groovy-the-superb-homeless.mally.pl/#/">Great introduction to Groovy</a> by Michał Mally at the Groovy user group in Poland</li>
<li><a href="http://www.infoq.com/presentations/groovy-for-java">Groovy for Java developers</a>, by Peter Ledbrook, recorded during SpringOne2GX 2014</li>
<li><a href="http://www.infoq.com/presentations/geb">Groovy browser automation with Geb</a>, by Colin Harrington, recorded at SpringOne2GX 2014</li>
<li>A video introduction to <a href="https://www.youtube.com/watch?v=cD7NPxuuXYY">Gradle for Android Studio</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau is annoyed when <a href="https://twitter.com/CedricChampeau/status/576062386085433345">people reduce Groovy to just a &ldquo;dynamic&rdquo; language when it&rsquo;s much more</a> than that</li>
<li>Ken Kousen tasting a <a href="https://twitter.com/kenkousen/status/576904271163232257">Groovy Smoothie</a></li>
<li>Matthew McCullough <a href="https://twitter.com/matthewmccull/status/576062271924891649">Groovy JMeter plugin is moving to Github</a>, like the many Google Code projects following Google&rsquo;s closing</li>
<li><a href="https://twitter.com/grooscript/status/576858409678540800">GrooScript plugin working for Grails 3 RC-1</a></li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>The <a href="http://mail-archives.apache.org/mod_mbox/incubator-general/201503.mbox/%3CCA+ULb+tHEc5N9vDvu526Jmb=te4F3yCwCWWJxxMC07EAz_35wQ@mail.gmail.com%3E">Groovy incubation proposal is being discussed on the Apache</a> mailing-lists</li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Ken Kousen published <a href="https://twitter.com/kenkousen/status/576067688931454977">Groovy Podcast episode 8</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Using <a href="https://gist.github.com/chiquitinxx/ef2d33ce9d1a810ba216">RxJs from GrooScript with Groovy&rsquo;s @BaseScript</a> transformation</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>You can find Ken Kousen&rsquo;s <a href="https://twitter.com/manningbooks/status/577487522990714881">Making Java Groovy book code samples</a> on Manning&rsquo;s website</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/springone2gx/status/576765439348318208">Registration for SpringOne2GX</a> 2015 is open</li>
<li>Guillaume Laforge will be speaking about <a href="https://twitter.com/jfrog/status/576052612144885762">Groovy&rsquo;s continuous delivery at the SwampUp conference</a></li>
<li>At the Gradle Summit, <a href="https://twitter.com/gradleware/status/577527498281472000">Netflix&rsquo; build team will share lessons learned about Gradle plugins</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #61</title><link>https://glaforge.dev/posts/2015/03/10/groovy-weekly-61/</link><pubDate>Tue, 10 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/10/groovy-weekly-61/</guid><description>&lt;p>Big news for this week with the intention of the &lt;a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">Groovy project to join the Apache Software Foundation&lt;/a>! And if you wnat to know more about who actually &lt;a href="http://melix.github.io/blog/2015/02/who-is-groovy.html">contributed to Groovy over the years&lt;/a>, Cédric detailed that on his blog.&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://gmavenplus.56682.x6.nabble.com/gmavenplus-announce-GMavenPlus-1-5-Released-td222.html">GMavenPlus 1.5&lt;/a> released&lt;/li>
&lt;li>&lt;a href="http://grooscript.org/changes.html">GrooScript 1.0.1&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/572876188902813697">GrooScript Gradle plugin 1.0.1&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/andrewreitz_/status/575138522077659136">Android-Spock 1.2&lt;/a> released working with Spock 1.0&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/574982347768119296">GVM 2.3.0&lt;/a> released with new “outdated” command to know if newer candidates are available&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Guillaume Laforge announces the intention of the &lt;a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">Groovy project to join the Apache Software Foundation&lt;/a>&lt;/li>
&lt;li>Cédric Champeau wrote a very interesting and insightful &lt;a href="http://melix.github.io/blog/2015/02/who-is-groovy.html">analysis on who contributed to Groovy&lt;/a> over the years&lt;/li>
&lt;li>John K. Waters from ADTMag on &lt;a href="http://adtmag.com/articles/2015/03/04/groovy-joins-apache-foundation.aspx">Groovy to join Apache Software Foundation&lt;/a> following up an interview with Guillaume Laforge&lt;/li>
&lt;li>Jochen Theodorou brainstorms on the path &lt;a href="http://blackdragsview.blogspot.fr/2015/03/thoughts-about-new-meta-class-system.html">towards a revamped MOP for Groovy&lt;/a>&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Awesome Asciidoctor: &lt;a href="http://mrhaki.blogspot.fr/2015/03/awesome-asciidoctor-use-inline.html">Use inline extension DSL with Gradle&lt;/a>&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Gradle Goodness: &lt;a href="http://mrhaki.blogspot.fr/2015/03/gradle-goodness-define-system.html">Define system properties in gradle.properties&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://blog.jetbrains.com/idea/2015/03/new-features-for-gradle-coming-in-intellij-idea-14-1/">New features for Gradle Coming in IntelliJ IDEA 14.1&lt;/a> by Andrey Cheptsov&lt;/li>
&lt;li>&lt;a href="http://grooscript.org/react_example.html">Groovy, GrooScript with React.JS&lt;/a>&lt;/li>
&lt;li>Marcin Zajączkowski on &lt;a href="https://solidsoft.wordpress.com/2015/03/09/spock-1-0-with-groovy-2-4-configuration-comparison-in-maven-and-gradle/">Spock 1.0 with Groovy 2.4 configuration comparison in Maven and Gradle&lt;/a>&lt;/li>
&lt;li>Benoît Hédiard writes about how to &lt;a href="https://medium.com/@benorama/how-to-publish-your-grails-3-plugin-to-bintray-c341b24f567d">publish your Grails 3 plugin to Bintray&lt;/a>&lt;/li>
&lt;li>The Ratpack team published more information on &lt;a href="http://www.ratpack.io/manual/0.9.15/intro.html#about_this_documentation">how the Ratpack documentation is working&lt;/a> and how code samples are tested&lt;/li>
&lt;li>Ken Kousen in &amp;ldquo;&lt;a href="https://kousenit.wordpress.com/2015/03/10/if-a-method-arg-is-a-closure-for-crying-out-loud-pass-it-a-closure/">If a method arg is a closure, for crying out loud pass it a closure&lt;/a>&amp;rdquo;&lt;/li>
&lt;li>&lt;a href="http://blog.jetbrains.com/idea/2015/03/faster-groovy-compilation-in-intellij-idea-14-1/">Faster Groovy Compilation in IntelliJ IDEA&lt;/a> 14.1 by Peter Gromov&lt;/li>
&lt;li>Grails tip: &lt;a href="http://blog.jdriven.com/2015/03/grails-tip-rendering-errors-of-multiple-beans/">Rendering errors of multiple beans&lt;/a> by Albert van Veen&lt;/li>
&lt;li>Get Groovy with &lt;a href="http://zeroturnaround.com/blog/get-groovy-with-jrebel-and-grails/">JRebel and Grails&lt;/a> by Adam Koblentz&lt;/li>
&lt;li>Working with &lt;a href="http://www.intelligrape.com/blog/working-with-sass-scripts-in-grails-2-3-x/">SASS scripts in Grails 2.3&lt;/a> by Mansi Arora&lt;/li>
&lt;li>&lt;a href="http://www.objectpartners.com/2015/03/03/customize-your-grails-test-reports/">Customize your Grails test reports&lt;/a> by Igor Shults&lt;/li>
&lt;li>&lt;a href="http://www.objectpartners.com/2015/03/06/another-method-for-admin-screens-with-grails/">Another method for admin screens with Grails&lt;/a> by Mike Hostetler&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>The &lt;a href="https://twitter.com/marc0der/status/573405378744098817">GVM project just reached 400 stars on Github&lt;/a>, keep star&amp;rsquo;ing!&lt;/li>
&lt;li>New &lt;a href="http://docs.groovy-lang.org/next/html/documentation/core-object-orientation.html#_meta_annotations">documentation on Groovy meta-annotations&lt;/a> authored by Cédric Champeau&lt;/li>
&lt;li>Kunal Dabir created a simple &lt;a href="https://github.com/kdabir/directree">DSL to create directory trees and files&lt;/a>&lt;/li>
&lt;li>An updated &lt;a href="http://grooscript.org/roadmap.html">roadmap for GrooScript&lt;/a>, including support for require.js&lt;/li>
&lt;li>Jacob Aae Mikkelsen&amp;rsquo;s &lt;a href="http://grydeske.net/news/show/86">Grails Diary&lt;/a> week 10 of 2015&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Thanks to Luke Daley and Will Erickson, &lt;a href="https://twitter.com/CedricChampeau/status/573763143593295872">Dagger 2 will work out of the box with Groovy sources and Gradle 2.4&lt;/a>&lt;/li>
&lt;li>Cédric Champeau documented how you can &lt;a href="https://twitter.com/CedricChampeau/status/573583638127845376">use closures as annotation parameters&lt;/a>&lt;/li>
&lt;li>Cédric Champeau is so much into AST transformations that he wrongly coined the &lt;a href="https://twitter.com/CedricChampeau/status/573522774758662144">Apache Software Foundation with the AST acronym&lt;/a> instead of the ASF&lt;/li>
&lt;li>Using &lt;a href="https://twitter.com/davidthecoder/status/574223398945091584">Reactor with Groovy is a nice experience in data processing&lt;/a> says David Dawson&lt;/li>
&lt;li>Dan Woods reminds us that &lt;a href="https://twitter.com/danveloper/status/574298666623614976">Ratpack has a forum&lt;/a> in case you need help getting started&lt;/li>
&lt;li>Tim Yates found some &lt;a href="https://twitter.com/tim_yates/status/574270524139945984">Groovy biscuits&lt;/a> for the discerning Groovy developer&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Using &lt;a href="https://gist.github.com/chiquitinxx/b01d0696cfabcc34527b">RxJs with GrooScript&lt;/a>&lt;/li>
&lt;li>Iván López authored a handy Groovy &lt;a href="https://gist.github.com/lmivan/a752644419fb8d2f2221">script to generate the HTML of the Greach conference speakers pages&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>GR8Conf Europe 2015 announces a &lt;a href="http://gr8conf.eu/#/">first batch of speakers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Big news for this week with the intention of the <a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">Groovy project to join the Apache Software Foundation</a>! And if you wnat to know more about who actually <a href="http://melix.github.io/blog/2015/02/who-is-groovy.html">contributed to Groovy over the years</a>, Cédric detailed that on his blog.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://gmavenplus.56682.x6.nabble.com/gmavenplus-announce-GMavenPlus-1-5-Released-td222.html">GMavenPlus 1.5</a> released</li>
<li><a href="http://grooscript.org/changes.html">GrooScript 1.0.1</a> released</li>
<li><a href="https://twitter.com/grooscript/status/572876188902813697">GrooScript Gradle plugin 1.0.1</a> released</li>
<li><a href="https://twitter.com/andrewreitz_/status/575138522077659136">Android-Spock 1.2</a> released working with Spock 1.0</li>
<li><a href="https://twitter.com/gvmtool/status/574982347768119296">GVM 2.3.0</a> released with new “outdated” command to know if newer candidates are available</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Guillaume Laforge announces the intention of the <a href="https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/">Groovy project to join the Apache Software Foundation</a></li>
<li>Cédric Champeau wrote a very interesting and insightful <a href="http://melix.github.io/blog/2015/02/who-is-groovy.html">analysis on who contributed to Groovy</a> over the years</li>
<li>John K. Waters from ADTMag on <a href="http://adtmag.com/articles/2015/03/04/groovy-joins-apache-foundation.aspx">Groovy to join Apache Software Foundation</a> following up an interview with Guillaume Laforge</li>
<li>Jochen Theodorou brainstorms on the path <a href="http://blackdragsview.blogspot.fr/2015/03/thoughts-about-new-meta-class-system.html">towards a revamped MOP for Groovy</a></li>
<li>MrHaki&rsquo;s Awesome Asciidoctor: <a href="http://mrhaki.blogspot.fr/2015/03/awesome-asciidoctor-use-inline.html">Use inline extension DSL with Gradle</a></li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2015/03/gradle-goodness-define-system.html">Define system properties in gradle.properties</a></li>
<li><a href="http://blog.jetbrains.com/idea/2015/03/new-features-for-gradle-coming-in-intellij-idea-14-1/">New features for Gradle Coming in IntelliJ IDEA 14.1</a> by Andrey Cheptsov</li>
<li><a href="http://grooscript.org/react_example.html">Groovy, GrooScript with React.JS</a></li>
<li>Marcin Zajączkowski on <a href="https://solidsoft.wordpress.com/2015/03/09/spock-1-0-with-groovy-2-4-configuration-comparison-in-maven-and-gradle/">Spock 1.0 with Groovy 2.4 configuration comparison in Maven and Gradle</a></li>
<li>Benoît Hédiard writes about how to <a href="https://medium.com/@benorama/how-to-publish-your-grails-3-plugin-to-bintray-c341b24f567d">publish your Grails 3 plugin to Bintray</a></li>
<li>The Ratpack team published more information on <a href="http://www.ratpack.io/manual/0.9.15/intro.html#about_this_documentation">how the Ratpack documentation is working</a> and how code samples are tested</li>
<li>Ken Kousen in &ldquo;<a href="https://kousenit.wordpress.com/2015/03/10/if-a-method-arg-is-a-closure-for-crying-out-loud-pass-it-a-closure/">If a method arg is a closure, for crying out loud pass it a closure</a>&rdquo;</li>
<li><a href="http://blog.jetbrains.com/idea/2015/03/faster-groovy-compilation-in-intellij-idea-14-1/">Faster Groovy Compilation in IntelliJ IDEA</a> 14.1 by Peter Gromov</li>
<li>Grails tip: <a href="http://blog.jdriven.com/2015/03/grails-tip-rendering-errors-of-multiple-beans/">Rendering errors of multiple beans</a> by Albert van Veen</li>
<li>Get Groovy with <a href="http://zeroturnaround.com/blog/get-groovy-with-jrebel-and-grails/">JRebel and Grails</a> by Adam Koblentz</li>
<li>Working with <a href="http://www.intelligrape.com/blog/working-with-sass-scripts-in-grails-2-3-x/">SASS scripts in Grails 2.3</a> by Mansi Arora</li>
<li><a href="http://www.objectpartners.com/2015/03/03/customize-your-grails-test-reports/">Customize your Grails test reports</a> by Igor Shults</li>
<li><a href="http://www.objectpartners.com/2015/03/06/another-method-for-admin-screens-with-grails/">Another method for admin screens with Grails</a> by Mike Hostetler</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="https://twitter.com/marc0der/status/573405378744098817">GVM project just reached 400 stars on Github</a>, keep star&rsquo;ing!</li>
<li>New <a href="http://docs.groovy-lang.org/next/html/documentation/core-object-orientation.html#_meta_annotations">documentation on Groovy meta-annotations</a> authored by Cédric Champeau</li>
<li>Kunal Dabir created a simple <a href="https://github.com/kdabir/directree">DSL to create directory trees and files</a></li>
<li>An updated <a href="http://grooscript.org/roadmap.html">roadmap for GrooScript</a>, including support for require.js</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/86">Grails Diary</a> week 10 of 2015</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Thanks to Luke Daley and Will Erickson, <a href="https://twitter.com/CedricChampeau/status/573763143593295872">Dagger 2 will work out of the box with Groovy sources and Gradle 2.4</a></li>
<li>Cédric Champeau documented how you can <a href="https://twitter.com/CedricChampeau/status/573583638127845376">use closures as annotation parameters</a></li>
<li>Cédric Champeau is so much into AST transformations that he wrongly coined the <a href="https://twitter.com/CedricChampeau/status/573522774758662144">Apache Software Foundation with the AST acronym</a> instead of the ASF</li>
<li>Using <a href="https://twitter.com/davidthecoder/status/574223398945091584">Reactor with Groovy is a nice experience in data processing</a> says David Dawson</li>
<li>Dan Woods reminds us that <a href="https://twitter.com/danveloper/status/574298666623614976">Ratpack has a forum</a> in case you need help getting started</li>
<li>Tim Yates found some <a href="https://twitter.com/tim_yates/status/574270524139945984">Groovy biscuits</a> for the discerning Groovy developer</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Using <a href="https://gist.github.com/chiquitinxx/b01d0696cfabcc34527b">RxJs with GrooScript</a></li>
<li>Iván López authored a handy Groovy <a href="https://gist.github.com/lmivan/a752644419fb8d2f2221">script to generate the HTML of the Greach conference speakers pages</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>GR8Conf Europe 2015 announces a <a href="http://gr8conf.eu/#/">first batch of speakers</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Projects intends to join the Apache Software Foundation</title><link>https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/</link><pubDate>Wed, 04 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/04/groovy-projects-intends-to-join-the-apache-software-foundation/</guid><description>&lt;p>The Groovy team is happy to announce its intention to join the Apache Software Foundation (ASF).&lt;/p>
&lt;p>Following up the recent announcement from Pivotal to end funding of full time developers for the Groovy programming language project, the team thought it would be appropriate to demonstrate to the Groovy community that the project is here for the long term, regardless of any funding from particular sponsoring organizations and regardless of any changes to the team of committers over time.&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy team is happy to announce its intention to join the Apache Software Foundation (ASF).</p>
<p>Following up the recent announcement from Pivotal to end funding of full time developers for the Groovy programming language project, the team thought it would be appropriate to demonstrate to the Groovy community that the project is here for the long term, regardless of any funding from particular sponsoring organizations and regardless of any changes to the team of committers over time.</p>
<p>We had several discussions both online and offline with representatives of various foundations, in particular with the Eclipse Foundation, the Software Conservancy foundation and the ASF.</p>
<p>We’ve been very grateful for the time and advice given by everybody, our users, committers to projects of those foundations, board members, etc. Those discussions have been very fruitful and enlightening. Those foundations are all very interesting and could be a great fit for the project, even if all have their pros and cons, of course. But overall, the Apache Software Foundation is the one that appears to be the best candidate considering our constraints and our philosophy.</p>
<p>We will soon start the process of submitting a proposal for incubation. If the proposal is accepted, the discussions we had on the mailing list highlighted some grey areas that we are going to deal with during that incubation process and we will be very happy to work with our mentors to make sure Groovy remains a major OSS project in the JVM ecosystem.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #60</title><link>https://glaforge.dev/posts/2015/03/03/groovy-weekly-60/</link><pubDate>Tue, 03 Mar 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/03/03/groovy-weekly-60/</guid><description>&lt;p>This round number edition is pretty busy!&lt;/p>
&lt;p>Last week ended with a pretty sad note, with the news of Leonard Nimoy’s passing, the famous vulcan extraterrestrial from Star Trek. This certainly resonates with the Groovy community (and geeks &amp;amp; fans out large) because of our very own Spock testing framework. So let’s all gather and have a thought for this great actor and for his family.&lt;/p>
&lt;p>But as a tribute to this great actor and character, the long awaited news is that the &lt;a href="http://spockframework.github.io/spock/docs/1.0/release_notes.html">Spock testing framework releases its 1.0 version&lt;/a>!&lt;/p></description><content:encoded>
<![CDATA[<p>This round number edition is pretty busy!</p>
<p>Last week ended with a pretty sad note, with the news of Leonard Nimoy’s passing, the famous vulcan extraterrestrial from Star Trek. This certainly resonates with the Groovy community (and geeks &amp; fans out large) because of our very own Spock testing framework. So let’s all gather and have a thought for this great actor and for his family.</p>
<p>But as a tribute to this great actor and character, the long awaited news is that the <a href="http://spockframework.github.io/spock/docs/1.0/release_notes.html">Spock testing framework releases its 1.0 version</a>!</p>
<p>Still about releases, <a href="http://new.griffon-framework.org/news/griffon_2.2.0.html">Griffon 2.2</a> is out! As well as the last milestone of Grails 3 before it reaches release candidate status.</p>
<p>Following up the loss of funding for the Groovy and Grails projects, <a href="http://restlet.com/blog/2015/03/02/head-of-groovy-project-joins-restlet-to-lead-api-development-tools/">Guillaume Laforge, lead of Groovy, joins Restlet, a startup dedicated to API software</a>.</p>
<p>Another important news for the Groovy community is the <a href="http://www.codehaus.org/">closing of Codehaus</a>, which was the birth place of many projects from the ecosystem, including Groovy, Grails, Griffon, GPars, Geb, and others. All projects with some assets remaining there will have to migrate them before mid-May.</p>
<p>I’d like to congratulate Tim Yates for being so awesome and helpful on Stack Overflow, as he’s accounted that he replied to 14% of all questions tagged Groovy!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://spockframework.github.io/spock/docs/1.0/release_notes.html">Spock 1.0</a> released!</li>
<li><a href="http://new.griffon-framework.org/news/griffon_2.2.0.html">Griffon 2.2</a> released</li>
<li><a href="https://twitter.com/grailsframework/status/570976462439878656">Grails 3.0 milestone 2</a> released</li>
<li><a href="http://www.ratpack.io/versions/0.9.14">Ratpack 0.9.14</a> released</li>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Announcing-CodeNarc-0-23-td5722800.html">CodeNarc 0.23</a> released</li>
<li><a href="https://twitter.com/gvmtool/status/572008212712587264">GVM 2.2.1</a> released</li>
<li><a href="https://spring.io/blog/2015/02/27/spring-boot-1-1-11-released">Spring Boot 1.1.11</a> released</li>
<li><a href="https://spring.io/blog/2015/02/27/spring-boot-1-2-2-released">Spring Boot 1.2.2</a> released</li>
<li><a href="https://twitter.com/CedricChampeau/status/570995951835312128">JMH Gradle plugin 0.2.0</a> released4financeIT <a href="https://twitter.com/MGrzejszczak/status/570600241604509696">Gradle UpToDate plugin 1.2.0</a> release with proxy supportA Gradle plugin to <a href="http://android-arsenal.com/details/1/1609">upload Android APKs and app details to the Google Play Store</a></li>
<li><a href="https://twitter.com/AndreyHihlovski/status/572799840754782208">Gretty Gradle plugin 1.1.9</a> for running webapps on Tomcat &amp; Jetty from Gradle</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Head of Groovy project, <a href="http://restlet.com/blog/2015/03/02/head-of-groovy-project-joins-restlet-to-lead-api-development-tools/">Guillaume Laforge, joins Restlet</a> to lead API development tools</li>
<li>Guillaume Laforge&rsquo;s first post at his new gig about <a href="http://restlet.com/blog/2015/03/02/lets-make-apis-groovy-er/">making APIs groovy-er</a></li>
<li>The Register writes about &ldquo;<a href="http://www.theregister.co.uk/2015/03/02/groovy_java_guy_off_for_restlet/">Groovy Java guy off for Restlet</a>&rdquo;</li>
<li>Schalk Cronjé remarks that the <a href="https://twitter.com/ysb33r/status/572420138185715712">increasing number of Twitter congrats pouring out for Guillaume Laforge is a sign of the respect the Groovy community has for the man</a></li>
<li>Bertrand Delacretaz says that with half a million user, the project lead not fulltime anymore, <a href="https://twitter.com/bdelacretaz/status/572426911491756032">it&rsquo;s time that Groovy finds a neutral home</a></li>
<li>Graeme Rocher gives an <a href="http://grails.io/post/112130778738/grails-project-infrastructure-update">update on the Grails project infrastructure</a></li>
<li>A user guide was published explaining how to <a href="http://grails.github.io/grails-doc/3.0.x/guide/upgrading.html">migrate from Grails 2 to Grails 3</a></li>
<li>Kohsuke Kawaguchi suggests the Groovy team to <a href="http://kohsuke.org/2015/02/27/groovy-folks-time-to-start-agreeing/">start agreeing on a choice of a foundation</a></li>
<li>Danny Hyun describes how to use <a href="http://danhyun.github.io/asciidoctor-gradle-examples/">Gradle and Asciidoctor for HTML, PDF, ePUB, Deck.JS, Reveal.JS</a> outputs</li>
<li>How to <a href="http://planet.jboss.org/post/how_to_configure_geb_spock_with_gradle">configure Geb and Spock with Gradle</a></li>
<li><a href="http://www.eclecticlogic.com/2015/02/17/groovy-oddities/">Groovy oddities</a> by Karthik Abram</li>
<li>The Griffon team published the <a href="http://new.griffon-framework.org/releasenotes/griffon_2.2.0.html">Griffon 2.2 release notes</a></li>
<li>First <a href="http://new.griffon-framework.org/tutorials/">Griffon 2.2 tutorials</a> available</li>
<li>Marcin Grzejszczak on how he <a href="http://toomuchcoding.blogspot.com/2015/02/how-to-speed-up-your-gradle-build-from.html">reduced his Gradle build from 90 to 8 minutes</a></li>
<li>Craig Burke updates his <a href="http://www.craigburke.com/2015/03/02/groovy-document-builder-mpl-edition.html">Groovy Document Builder</a> with a friendly OSS license</li>
<li>Kyle Boon has up&rsquo;ed his Spock dependency to 1.0</li>
<li>After Spock 1.0, Iván López wonders <a href="https://twitter.com/ilopmar/status/572424545426116608">which framework will get to 1.0: Geb or Ratpack</a>?</li>
<li>Arthur Tsang blogs about the <a href="http://arthur-notes.youramaryllis.com/2015/02/moving-from-ratpack-0912-to-0913.html">changes required for moving from Ratpack 0.9.12 to 0.9.13</a> with the new configuration changes</li>
<li><a href="https://twitter.com/GebFramework/status/572689714760835072">Geb is now using Spock 1.0</a> just released</li>
<li>Dan Woods announces slick <a href="https://twitter.com/danveloper/status/572764621007814656">Groovy extensions for RxJava and &lsquo;ReactiveStreams in Ratpack&rsquo;s next version</a></li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Victor Grazi of <a href="http://www.infoq.com/news/2015/03/Groovys-Laforge-Joins-Restlet">InfoQ interviewed Guillaume Laforge</a> on the fact he&rsquo;s joining the Restlet startup</li>
<li>Voxxed <a href="https://www.voxxed.com/blog/2015/03/with-foundation-decision-imminent-groovy-creator-moves-to-restlet/">interviews Guillaume Laforge with the imminent move of Groovy to a foundation and with joining Restlet</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Cédric Champeau presented <a href="https://twitter.com/CedricChampeau/status/572428885708697601">Groovy and Android, a winning pair</a>, at MCE 2015</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://www.codehaus.org/">Codehaus is shutting its doors</a></li>
<li>Andy Clément announces that <a href="https://twitter.com/andy_clement/status/570989272406233089">Groovy Eclipse 2.9.2 builds for Eclipse 4.4 and includes Groovy 2.3.10 and 2.4.1</a></li>
<li>Søren Berg Glasius and Guillaume Laforge <a href="https://twitter.com/glaforge/status/571044985463803904">collaborated on the GR8Conf Europe 2015 agenda</a></li>
<li><a href="http://groovy.329449.n5.nabble.com/GroovyLab-on-Raspberry-Pi-2-td5722839.html">GroovyLab now works on Raspberry Pi 2</a> by Stergios Papadimitriou</li>
<li><a href="https://grails.org/plugin/react-asset-pipeline">React support in Grails Asset Pipeline</a> plugin</li>
<li>With Codehaus closing, <a href="https://twitter.com/GebFramework/status/572305358913331201">Geb has moved its mailing-lists to Google Groups</a></li>
<li><a href="http://www.ratpack.io/manual/0.9.15/api/ratpack/logging/MDCInterceptor.html">Ratpack 0.9.14 adds support for Slf4j&rsquo;s Mapped Diagnostic Context</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Congrats to <a href="https://twitter.com/tim_yates/status/571231496004108288">Tim Yates who posted 14% of all answers to Stack Overflow questions on Groovy</a></li>
<li>The <a href="https://twitter.com/spockframework/status/572378381146300418">Spock Framework has its own twitter account</a></li>
<li>In memory of Leonard Nimoy&rsquo;s passing last week, Guillaume Laforge <a href="https://twitter.com/glaforge/status/571393595825111041">wishes the Spock framework to &ldquo;live long and prosper&rdquo;</a></li>
<li>Cédric Champeau outlines the fact that <a href="https://twitter.com/CedricChampeau/status/572412160304934912">Guillaume Laforge will continue to work on Groovy</a></li>
<li>With Codehaus closing, <a href="https://twitter.com/GebFramework/status/571234105313988609">Geb is going to move its mailing-list and issue tracker</a> soon</li>
<li>Marco Vermeulen continues working on <a href="https://twitter.com/marc0der/status/571765090342596609">GVM&rsquo;s Vendor API</a></li>
<li>Billy Yarosh <a href="https://twitter.com/keaplogik/status/570385013042225152">enjoys Groovy&rsquo;s documentation</a></li>
<li><a href="https://twitter.com/gvmtool/status/570979574768652288">Grails 3 milestone 2</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/571217914944221185">Spring Boot 1.2.2</a> is available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/571218056095137792">Spring Boot 1.1.11</a> is available on GVM</li>
<li>Peter Niederwieser choses <a href="https://twitter.com/pniederw/status/572107004510126082">TravisCI for Spock&rsquo;s continuous integration</a> solution</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>The <a href="https://twitter.com/grooscript/status/571730552564420608">GrooScript project reached 150 followers on twitter</a>, let&rsquo;s add more followers!</li>
<li><a href="https://gist.github.com/chiquitinxx/a38ba0b54a405c11ece4">Mixing Angular.JS and GrooScript</a> using closures for controllers</li>
<li>Cédric Champeau created a <a href="https://twitter.com/CedricChampeau/status/571257801630220288">file / directory structure builder</a></li>
<li>Russell Hart shows how to <a href="https://github.com/ratpack/example-books/blob/latest/src/ratpack/ratpack.groovy#L44">externalize Ratpack&rsquo;s metrics collection configuration</a></li>
<li>Benoit Hediard on using <a href="https://gist.github.com/benorama/b26e7a94fe80e8c54daf">Grails on Amazon Beanstalk with TravisCI</a></li>
<li>A <a href="https://gist.github.com/chiquitinxx/e0c561547e0e1b690767">trait approach for GrooScript with Angular.JS</a> controllers</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>A screencast introducing <a href="https://vimeo.com/zeroturnaround/review/120679908/75a58f2e13">JRebel&rsquo;s improved Grails support</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Dierk König notes that even the <a href="https://twitter.com/mittie/status/572497775872630784">small code listings in Groovy in Action 2nd edition makes for a 3MB archive</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Most <a href="https://twitter.com/sbglasius/status/571045481666605056">speakers for GR8Conf Europe 2015</a> have been picked up</li>
<li><a href="https://twitter.com/alvaro_sanchez/status/571248810309095424">Álvaro Sánchez will be speaking at GR8Conf Europe</a> 2015</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #59</title><link>https://glaforge.dev/posts/2015/02/24/groovy-weekly-59/</link><pubDate>Tue, 24 Feb 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/02/24/groovy-weekly-59/</guid><description>&lt;p>Groovy keeps rocking with a &lt;a href="https://glaforge.dev/posts/2015/02/18/joint-releases-of-groovy-2-4-1-and-2-3-10/">joint release of Groovy 2.4.1 and also a 2.3.10&lt;/a>!&lt;/p>
&lt;p>And did you notice the nice new design of the &lt;a href="https://grails.org/">new Grails website&lt;/a>, based on &lt;a href="http://www.groovy-lang.org/">Groovy’s website&lt;/a>? That’s a family look!&lt;/p>
&lt;p>Also, I’d like to end this short editorial with a big congrats to the &lt;a href="https://twitter.com/gvmtool/status/570138706973224960">GVM team who’s reached the 100,000 unique installs&lt;/a>! That’s a big milestone!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>Joint releases of &lt;a href="https://glaforge.dev/posts/2015/02/18/joint-releases-of-groovy-2-4-1-and-2-3-10/">Groovy 2.4.1 and Groovy 2.3.10&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://groovy.329449.n5.nabble.com/GMavenPlus-1-4-Released-td5722753.html">GMavenPlus 1.4&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Voxxed covers the &lt;a href="https://www.voxxed.com/blog/2015/02/groovy-latest-double-release-drop-foundation-deliberations/">latest double Groovy release drops and the deliberations on the choice of a foundation&lt;/a> for the project&lt;/li>
&lt;li>Shane Curcuru reflected on the fact of &lt;a href="https://medium.com/@shanecurcuru/its-groovy-to-join-a-foundation-4145732b4ec1">Groovy joining a foundation&lt;/a>&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Groovy Goodness: &lt;a href="http://mrhaki.blogspot.fr/2015/02/groovy-goodness-access-xml-rpc-api.html">Access XML-RPC API&lt;/a>&lt;/li>
&lt;li>Kyle Boon on &lt;a href="http://kyleboon.org/blog/2015/02/23/groovy-gvm-and-go-gvm/">using Groovy and Go&amp;rsquo;s GVMs together&lt;/a>&lt;/li>
&lt;li>Installing &lt;a href="http://www.intelligrape.com/blog/sonarqube-all-in-one-code-quality-manager/">SonarQube for managing the quality of your Grails&lt;/a> project&lt;/li>
&lt;li>Miguel de la Cruz teaches a &lt;a href="http://www.kaleidos.net/blog/993/geb-101-simple-scripts/">Geb 101 course&lt;/a> in this article&lt;/li>
&lt;li>Laurent Napias wrote an article in French on &lt;a href="http://www.laurent-napias.com/post/2015/02/22/plus-de-groovy-avec-android-et-un-peu-de-swissknife-dedans">Groovy on Android with the SwissKnife&lt;/a> library&lt;/li>
&lt;li>Andy Wilkinson wrote about &lt;a href="https://spring.io/blog/2015/02/23/better-dependency-management-for-gradle">better dependency management for Gradle&lt;/a>&lt;/li>
&lt;li>Dustin Marx on &lt;a href="http://java.dzone.com/articles/writing-groovys-groovyutilnode">writing Groovy&amp;rsquo;s XmlParser&amp;rsquo;s Node content as XML&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.intelligrape.com/blog/integrating-pubnub-with-grails/">Integrating PubNub with Grails&lt;/a> by Sumant Thakur&lt;/li>
&lt;li>Ted Vinke on starting a &lt;a href="https://tedvinke.wordpress.com/2015/02/19/starting-a-video-blog-grails-in-the-first8friday-series/">video blog on Grails&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>Guillaume Laforge submitted &lt;a href="https://twitter.com/glaforge/status/567675689575669760">Groovy to the Google Summer of Code&lt;/a> program&lt;/li>
&lt;li>&lt;a href="https://kousenit.wordpress.com/2015/02/22/now-co-hosting-the-groovy-podcast/">Ken Kousen is co-hosting the Groovy podcast&lt;/a> with Peter Ledbrook&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/grailsframework/status/568471340686180352">Grails team launches its new website&lt;/a>, based on Groovy’s new site design&lt;/li>
&lt;li>Jacob Aae Mikkelsen&amp;rsquo;s &lt;a href="http://grydeske.net/news/show/84">Grails Diary&lt;/a> week 8 of 2015&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/570138706973224960">GVM crossed the 100k unique installs&lt;/a> mark!&lt;/li>
&lt;li>Cédric Champeau is happy having &lt;a href="https://twitter.com/CedricChampeau/status/568009999122894848">released two versions of Groovy in only two hours&lt;/a>&lt;/li>
&lt;li>Dean Macaulay finds that &lt;a href="https://twitter.com/dean_macaulay/status/567622801943564289">mixing Grails and Intellij IDEA makes a world of productivity&lt;/a>&lt;/li>
&lt;li>Tim Yates &lt;a href="https://twitter.com/tim_yates/status/567764313252896768">Groovy-logic library is listed on the MiniKanren website&lt;/a>&lt;/li>
&lt;li>Russel Winder wonders &lt;a href="https://twitter.com/russel_winder/status/568008750340173824">how Groovy release can be &amp;ldquo;hot&amp;rdquo; as Groovy is &amp;ldquo;cool&amp;rdquo;&lt;/a>!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/568008971828768768">Groovy 2.3.10&lt;/a> is available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/568009006440194048">Groovy 2.4.1&lt;/a> is available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/568529775150510080">GrooScript encourages speakers to use its vintage logo&lt;/a>&lt;/li>
&lt;li>Russel Winder is &lt;a href="https://twitter.com/russel_winder/status/568139020879974402">looking for some CSS guru for redesigning the GPars website&lt;/a>&lt;/li>
&lt;li>Dierk König notes that &lt;a href="https://twitter.com/mittie/status/568709714105765890">Groovy is 4 times in JavaOne 2014&amp;rsquo;s rock stars&lt;/a>&lt;/li>
&lt;li>Cédric Champeau notes the &lt;a href="https://twitter.com/CedricChampeau/status/568769788102619136">progress on the new Groovy documentation but highlights lots of sections looking for help for completion&lt;/a>&lt;/li>
&lt;li>Cédric Champeau highlights this &lt;a href="https://twitter.com/CedricChampeau/status/568767762417364993">commit of the Groovy executable documentation on type checking extensions&lt;/a>&lt;/li>
&lt;li>Answering Peter Ledbrook and Ken Kousen on the Groovy podcast, Cédric mentions that &lt;a href="https://twitter.com/CedricChampeau/status/569189775707652097">Grails&amp;rsquo; new site is a static site on Github, forked from the Groovy website&lt;/a>&lt;/li>
&lt;li>Kostis Kapelonis teases about &lt;a href="https://twitter.com/codepipes/status/569067464853749760">Groovy giving a 75% reduction of code lines against Java&lt;/a>, as an excerpt of his Spock book&lt;/li>
&lt;li>Kostis Kapelonis thinks the &lt;a href="https://twitter.com/codepipes/status/569775343806091264">&amp;ldquo;Making Java Groovy&amp;rdquo; book from Ken Kousen is one of the best Groovy for Java developers book&lt;/a> he&amp;rsquo;s ever read&lt;/li>
&lt;li>Versions 1.4.3 and 1.4.4 of the &lt;a href="https://twitter.com/pickypg/status/569885478775861248">ElasticSearch Groovy client released with special support for Grails 2.x&lt;/a>&lt;/li>
&lt;li>Schalk Cronjé is astonished that &lt;a href="https://twitter.com/ysb33r/status/570168038848405504">some Java developers still don&amp;rsquo;t know about Spock, Gradle, Bintray and Artifactory&lt;/a>&lt;/li>
&lt;li>Erik Pragt is working on an &lt;a href="https://twitter.com/epragt/status/570014405515186176">IntelliJ IDEA plugin in Groovy to support Ionic framework&lt;/a>&lt;/li>
&lt;li>Dan Woods and Iván López discuss about &lt;a href="https://twitter.com/ilopmar/status/570113950391664641">LogBack&amp;rsquo;s Groovy DSL configuration&lt;/a> and the tool to convert XML configurations into Groovy&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Andrés Almiray shares his configuration for &lt;a href="https://gist.github.com/aalmiray/7369b977a68baca32e13">combining Asciidoctor, Ditaa based diagrams, PDF rendering, in your Gradle build&lt;/a>&lt;/li>
&lt;li>Vladimír Oraný is trying to &lt;a href="https://gist.github.com/musketyr/164bf82d4a07b3355a08">achieve better test isolation in Grails&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/568475830072041472">Groovy making 3D figures dance in the browser thanks to GrooScript&lt;/a> and Three.js&lt;/li>
&lt;li>Danny Hun shares how to use &lt;a href="https://github.com/danhyun/asciidoctor-gradle-examples/tree/master/asciidoc-to-revealjs-example">Asciidoctor, Gradle and Reveal.JS&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="podcasts">Podcasts&lt;/h2>
&lt;ul>
&lt;li>Peter Ledbrook and Ken Kousen recorded &lt;a href="https://twitter.com/pledbrook/status/568896651055771648">Groovy podcast episode 7&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>Konstantinos Kapelonis is authoring &lt;a href="https://twitter.com/mittie/status/567803572570828801">Java testing with Spock for Manning&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>The &lt;a href="https://twitter.com/springcentral/status/568948321047293953">SpringOne2GX 2015 call for paper is open&lt;/a>&lt;/li>
&lt;li>You can &lt;a href="http://gradlesummit.com/conference/santa_clara/2015/06/home">submit a talk to the Gradle Summit&lt;/a>&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/greachconf/status/568699197307920384">Greach conference has announced its final agenda&lt;/a>&lt;/li>
&lt;li>Andrew Reitz will be speaking about Groovy on Android at GR8Conf US&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Groovy keeps rocking with a <a href="https://glaforge.dev/posts/2015/02/18/joint-releases-of-groovy-2-4-1-and-2-3-10/">joint release of Groovy 2.4.1 and also a 2.3.10</a>!</p>
<p>And did you notice the nice new design of the <a href="https://grails.org/">new Grails website</a>, based on <a href="http://www.groovy-lang.org/">Groovy’s website</a>? That’s a family look!</p>
<p>Also, I’d like to end this short editorial with a big congrats to the <a href="https://twitter.com/gvmtool/status/570138706973224960">GVM team who’s reached the 100,000 unique installs</a>! That’s a big milestone!</p>
<h2 id="releases">Releases</h2>
<ul>
<li>Joint releases of <a href="https://glaforge.dev/posts/2015/02/18/joint-releases-of-groovy-2-4-1-and-2-3-10/">Groovy 2.4.1 and Groovy 2.3.10</a></li>
<li><a href="http://groovy.329449.n5.nabble.com/GMavenPlus-1-4-Released-td5722753.html">GMavenPlus 1.4</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Voxxed covers the <a href="https://www.voxxed.com/blog/2015/02/groovy-latest-double-release-drop-foundation-deliberations/">latest double Groovy release drops and the deliberations on the choice of a foundation</a> for the project</li>
<li>Shane Curcuru reflected on the fact of <a href="https://medium.com/@shanecurcuru/its-groovy-to-join-a-foundation-4145732b4ec1">Groovy joining a foundation</a></li>
<li>MrHaki&rsquo;s Groovy Goodness: <a href="http://mrhaki.blogspot.fr/2015/02/groovy-goodness-access-xml-rpc-api.html">Access XML-RPC API</a></li>
<li>Kyle Boon on <a href="http://kyleboon.org/blog/2015/02/23/groovy-gvm-and-go-gvm/">using Groovy and Go&rsquo;s GVMs together</a></li>
<li>Installing <a href="http://www.intelligrape.com/blog/sonarqube-all-in-one-code-quality-manager/">SonarQube for managing the quality of your Grails</a> project</li>
<li>Miguel de la Cruz teaches a <a href="http://www.kaleidos.net/blog/993/geb-101-simple-scripts/">Geb 101 course</a> in this article</li>
<li>Laurent Napias wrote an article in French on <a href="http://www.laurent-napias.com/post/2015/02/22/plus-de-groovy-avec-android-et-un-peu-de-swissknife-dedans">Groovy on Android with the SwissKnife</a> library</li>
<li>Andy Wilkinson wrote about <a href="https://spring.io/blog/2015/02/23/better-dependency-management-for-gradle">better dependency management for Gradle</a></li>
<li>Dustin Marx on <a href="http://java.dzone.com/articles/writing-groovys-groovyutilnode">writing Groovy&rsquo;s XmlParser&rsquo;s Node content as XML</a></li>
<li><a href="http://www.intelligrape.com/blog/integrating-pubnub-with-grails/">Integrating PubNub with Grails</a> by Sumant Thakur</li>
<li>Ted Vinke on starting a <a href="https://tedvinke.wordpress.com/2015/02/19/starting-a-video-blog-grails-in-the-first8friday-series/">video blog on Grails</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Guillaume Laforge submitted <a href="https://twitter.com/glaforge/status/567675689575669760">Groovy to the Google Summer of Code</a> program</li>
<li><a href="https://kousenit.wordpress.com/2015/02/22/now-co-hosting-the-groovy-podcast/">Ken Kousen is co-hosting the Groovy podcast</a> with Peter Ledbrook</li>
<li>The <a href="https://twitter.com/grailsframework/status/568471340686180352">Grails team launches its new website</a>, based on Groovy’s new site design</li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/84">Grails Diary</a> week 8 of 2015</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/gvmtool/status/570138706973224960">GVM crossed the 100k unique installs</a> mark!</li>
<li>Cédric Champeau is happy having <a href="https://twitter.com/CedricChampeau/status/568009999122894848">released two versions of Groovy in only two hours</a></li>
<li>Dean Macaulay finds that <a href="https://twitter.com/dean_macaulay/status/567622801943564289">mixing Grails and Intellij IDEA makes a world of productivity</a></li>
<li>Tim Yates <a href="https://twitter.com/tim_yates/status/567764313252896768">Groovy-logic library is listed on the MiniKanren website</a></li>
<li>Russel Winder wonders <a href="https://twitter.com/russel_winder/status/568008750340173824">how Groovy release can be &ldquo;hot&rdquo; as Groovy is &ldquo;cool&rdquo;</a>!</li>
<li><a href="https://twitter.com/gvmtool/status/568008971828768768">Groovy 2.3.10</a> is available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/568009006440194048">Groovy 2.4.1</a> is available on GVM</li>
<li><a href="https://twitter.com/grooscript/status/568529775150510080">GrooScript encourages speakers to use its vintage logo</a></li>
<li>Russel Winder is <a href="https://twitter.com/russel_winder/status/568139020879974402">looking for some CSS guru for redesigning the GPars website</a></li>
<li>Dierk König notes that <a href="https://twitter.com/mittie/status/568709714105765890">Groovy is 4 times in JavaOne 2014&rsquo;s rock stars</a></li>
<li>Cédric Champeau notes the <a href="https://twitter.com/CedricChampeau/status/568769788102619136">progress on the new Groovy documentation but highlights lots of sections looking for help for completion</a></li>
<li>Cédric Champeau highlights this <a href="https://twitter.com/CedricChampeau/status/568767762417364993">commit of the Groovy executable documentation on type checking extensions</a></li>
<li>Answering Peter Ledbrook and Ken Kousen on the Groovy podcast, Cédric mentions that <a href="https://twitter.com/CedricChampeau/status/569189775707652097">Grails&rsquo; new site is a static site on Github, forked from the Groovy website</a></li>
<li>Kostis Kapelonis teases about <a href="https://twitter.com/codepipes/status/569067464853749760">Groovy giving a 75% reduction of code lines against Java</a>, as an excerpt of his Spock book</li>
<li>Kostis Kapelonis thinks the <a href="https://twitter.com/codepipes/status/569775343806091264">&ldquo;Making Java Groovy&rdquo; book from Ken Kousen is one of the best Groovy for Java developers book</a> he&rsquo;s ever read</li>
<li>Versions 1.4.3 and 1.4.4 of the <a href="https://twitter.com/pickypg/status/569885478775861248">ElasticSearch Groovy client released with special support for Grails 2.x</a></li>
<li>Schalk Cronjé is astonished that <a href="https://twitter.com/ysb33r/status/570168038848405504">some Java developers still don&rsquo;t know about Spock, Gradle, Bintray and Artifactory</a></li>
<li>Erik Pragt is working on an <a href="https://twitter.com/epragt/status/570014405515186176">IntelliJ IDEA plugin in Groovy to support Ionic framework</a></li>
<li>Dan Woods and Iván López discuss about <a href="https://twitter.com/ilopmar/status/570113950391664641">LogBack&rsquo;s Groovy DSL configuration</a> and the tool to convert XML configurations into Groovy</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Andrés Almiray shares his configuration for <a href="https://gist.github.com/aalmiray/7369b977a68baca32e13">combining Asciidoctor, Ditaa based diagrams, PDF rendering, in your Gradle build</a></li>
<li>Vladimír Oraný is trying to <a href="https://gist.github.com/musketyr/164bf82d4a07b3355a08">achieve better test isolation in Grails</a></li>
<li><a href="https://twitter.com/grooscript/status/568475830072041472">Groovy making 3D figures dance in the browser thanks to GrooScript</a> and Three.js</li>
<li>Danny Hun shares how to use <a href="https://github.com/danhyun/asciidoctor-gradle-examples/tree/master/asciidoc-to-revealjs-example">Asciidoctor, Gradle and Reveal.JS</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Peter Ledbrook and Ken Kousen recorded <a href="https://twitter.com/pledbrook/status/568896651055771648">Groovy podcast episode 7</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Konstantinos Kapelonis is authoring <a href="https://twitter.com/mittie/status/567803572570828801">Java testing with Spock for Manning</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/springcentral/status/568948321047293953">SpringOne2GX 2015 call for paper is open</a></li>
<li>You can <a href="http://gradlesummit.com/conference/santa_clara/2015/06/home">submit a talk to the Gradle Summit</a></li>
<li>The <a href="https://twitter.com/greachconf/status/568699197307920384">Greach conference has announced its final agenda</a></li>
<li>Andrew Reitz will be speaking about Groovy on Android at GR8Conf US</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Joint releases of Groovy 2.4.1 and 2.3.10</title><link>https://glaforge.dev/posts/2015/02/18/joint-releases-of-groovy-2-4-1-and-2-3-10/</link><pubDate>Wed, 18 Feb 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/02/18/joint-releases-of-groovy-2-4-1-and-2-3-10/</guid><description>&lt;p>The Groovy development team is happy to announce the joint releases of the Groovy 2.4.1 and Groovy 2.3.10 of the Groovy programming language for the Java platform.&lt;/p>
&lt;p>Both releases are bug fix releases, and while Groovy 2.4.1 is the latest official stable branch, we thought it might be helpful to some projects who are still on the 2.3.x line to get a final release for that branch. But going forward, the 2.3.x branch won’t see any upcoming release.&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy development team is happy to announce the joint releases of the Groovy 2.4.1 and Groovy 2.3.10 of the Groovy programming language for the Java platform.</p>
<p>Both releases are bug fix releases, and while Groovy 2.4.1 is the latest official stable branch, we thought it might be helpful to some projects who are still on the 2.3.x line to get a final release for that branch. But going forward, the 2.3.x branch won’t see any upcoming release.</p>
<p>You can learn more about all the tickets closed by reading:</p>
<ul>
<li>
<p>the <a href="http://groovy-lang.org/changelogs/changelog-2.4.1.html">Groovy 2.4.1 changelog</a></p>
</li>
<li>
<p>the <a href="http://groovy-lang.org/changelogs/changelog-2.3.10.html">Groovy 2.3.10 changelog</a></p>
</li>
</ul>
<p>For reference, you can also read the <a href="http://groovy-lang.org/releasenotes/groovy-2.4.html">release notes of Groovy 2.4</a> if you haven&rsquo;t had a chance to since the announcement.</p>
<p>Head over to the <a href="http://www.groovy-lang.org/download.html">download section of the new Groovy website</a> to download the binary distribution, or update your dependencies accordingly with those new version numbers.</p>
<p>Thanks a lot for all those who contributed to this release!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #58</title><link>https://glaforge.dev/posts/2015/02/17/groovy-weekly-58/</link><pubDate>Tue, 17 Feb 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/02/17/groovy-weekly-58/</guid><description>&lt;p>The past week’s been pretty calm, in terms of news, but a few of them really stood out.&lt;/p>
&lt;p>&lt;a href="http://www.forbes.com/sites/danwoods/2015/02/11/how-a-netflix-tech-innovation-can-unleash-creativity-in-your-business/print/">Netflix again leverages Groovy for scripting its user experience API&lt;/a> (additional coverage following last week’s announcement).&lt;/p>
&lt;p>The Gradleware team delivered &lt;a href="http://forums.gradle.org/gradle/topics/gradle-2-3-released">Gradle 2.3&lt;/a>! Congrats guys!&lt;/p>
&lt;p>And closer to home, a very important discussion about &lt;a href="http://groovy.329449.n5.nabble.com/Moving-Groovy-to-a-Foundation-td5722483.html">moving Groovy into a foundation&lt;/a> has started, inviting members and representatives of different foundations to join and offer their views and experience on the matter.&lt;/p></description><content:encoded>
<![CDATA[<p>The past week’s been pretty calm, in terms of news, but a few of them really stood out.</p>
<p><a href="http://www.forbes.com/sites/danwoods/2015/02/11/how-a-netflix-tech-innovation-can-unleash-creativity-in-your-business/print/">Netflix again leverages Groovy for scripting its user experience API</a> (additional coverage following last week’s announcement).</p>
<p>The Gradleware team delivered <a href="http://forums.gradle.org/gradle/topics/gradle-2-3-released">Gradle 2.3</a>! Congrats guys!</p>
<p>And closer to home, a very important discussion about <a href="http://groovy.329449.n5.nabble.com/Moving-Groovy-to-a-Foundation-td5722483.html">moving Groovy into a foundation</a> has started, inviting members and representatives of different foundations to join and offer their views and experience on the matter.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-3-released">Gradle 2.3</a> released</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-3-rc-3-is-now-available-for-testing">Gradle 2.3-rc-3</a> is ready for testing</li>
<li><a href="https://groups.google.com/forum/#!topic/spockframework/008xHyM5DEI">Spock-reports 1.2.4</a> released</li>
<li><a href="https://www.linkedin.com/groupItem?view=&amp;item=5972153940031131649&amp;type=member&amp;gid=76751&amp;trk=eml-b2_anet_digest_weekly-hero-12-grouppost-0&amp;midToken=AQES0rgmUQCbHg&amp;fromEmail=fromEmail&amp;ut=3j0nkf-NtM9CE1">gServ 0.9.6</a> released, a Groovy-based container-less REST framework</li>
<li><a href="https://twitter.com/nagai_masato/status/566531438795698180">GBench 0.4.3</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>A Forbes article explains how a <a href="http://www.forbes.com/sites/danwoods/2015/02/11/how-a-netflix-tech-innovation-can-unleash-creativity-in-your-business/print/">Netflix innovation unleash the creativity in their business, thanks to Nicobar, their Groovy based scripting solution</a></li>
<li>Jaxenter on the ongoing discussion about <a href="http://jaxenter.com/will-groovy-soon-eclipse-apache-project-114536.html">moving the Groovy project to a foundation</a></li>
<li>Masato Nagai <a href="http://nagaimasato.blogspot.fr/2015/02/gbench-043-released-lets-benchmark-as.html?spref=tw">benchmarks Groovy&rsquo;s as operator</a> between Groovy 2.3 and 2.4, using GBench</li>
<li>Jeff Beck writes about <a href="http://beckje01.com/blog/2015/02/14/grails-integration-testing-of-complex-transactions/">Grails integration testing of complex transactions</a></li>
<li><a href="http://hosain.net/2015/02/07/getting-started-with-android-development-using-groovy-2.4-and-android-studio.html">Getting started with Android development using Groovy 2.4</a> and Android Studio</li>
<li><a href="http://www.jroller.com/ie/entry/releasing_a_project_to_maven">Releasing a project to Maven Central with Gradle</a> by John McClean</li>
<li><a href="http://davesmith.me//blog/2015/geb-gradle-rules.html">Geb and Gradle rules</a> by Dave Smith</li>
<li><a href="http://davesmith.me//blog/2015/gradle-devops-rules.html">Gradle rules</a> by Dave Smith</li>
</ul>
<h2 id="mailing-lists">Mailing-lists</h2>
<ul>
<li>An important discussion on <a href="http://groovy.329449.n5.nabble.com/Moving-Groovy-to-a-Foundation-td5722483.html">moving Groovy to a foundation</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://steveonjava.com/congratulations-to-the-2014-javaone-rock-stars/">Cédric Champeau becomes a JavaOne rock star</a> thanks to his excellent presentation on rethinking APIs with traits during JavaOne 2014</li>
<li>Duncan Dickinson launches his <a href="http://www.groovy-tutorial.org/">Groovy 2 tutorial series</a>, with 4 tutorials on getting started, variables, operators, and control flow statements</li>
<li>You can vote for an <a href="https://youtrack.jetbrains.com/issue/IDEA-136587">improvement to let Gradle-built IntelliJ IDEA projects support clickable source code files</a> from the output window</li>
<li><a href="http://grydeske.net/news/show/83">Grails Diary</a> Week 7 of 2015 by Jacob Aae Mikkelsen</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau calls for feedback and <a href="https://twitter.com/CedricChampeau/status/565548766946926592">seeks advice on the idea of moving Groovy to a foundation</a></li>
<li>Andrés Almiray publishes the first Griffon tutorial for the next iteration of the Griffon website</li>
<li>The <a href="https://twitter.com/marc0der/status/566758590472794112">GVM vendor API</a> is now in a dev environment</li>
<li>GrooScript updated its <a href="https://twitter.com/grooscript/status/565282808307150848">groovy ecosystem frameworks</a> page</li>
<li>The JFrog crew earned a <a href="https://twitter.com/jfrog/status/565589535862841346">JavaOne Rock Star title thanks to their Groovy puzzlers talk</a></li>
<li><a href="https://twitter.com/gvmtool/status/567274822141763584">Gradle 2.3</a> available through GVM</li>
<li>The Groovy puzzlers guys are <a href="https://twitter.com/groovypuzzlers/status/567316156411691009">looking for new Groovy puzzlers for their second season</a>!</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Tim Yates and Cédric Champeau are playing <a href="https://twitter.com/tim_yates/status/565490899963375616">Groovy code golf on Fibonacci</a></li>
<li>Tim Yates shares some <a href="https://gist.github.com/timyates/d97d613e4379434ff7a8">snippets combining Groovy and GroovyFX</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Episode 48 of the JavaPubHouse is about using <a href="https://twitter.com/fguime/status/566098122901041152">Groovy and Gradle for Java projects</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Iván López will be speaking about being a <a href="https://twitter.com/ilopmar/status/565806105042120704">full-stack Groovy developer</a> at the Spring.IO conference</li>
<li>Marco Vermeulen notes that <a href="https://twitter.com/marc0der/status/566136563214876672">SpringOne2GX keeps its 2GX suffix and that the CFP is open</a></li>
<li>Craig Burke will be talking about <a href="https://twitter.com/craigburke1/status/566637455982731264">Angular.JS in a Groovy world</a> at GR8Conf US</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #57</title><link>https://glaforge.dev/posts/2015/02/10/groovy-weekly-57/</link><pubDate>Tue, 10 Feb 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/02/10/groovy-weekly-57/</guid><description>&lt;p>So what’s Groovy this week? The Groovy community is still celebrating the 4.5+ million downloads of Groovy last year, is drive testing the upcoming Gradle 2.3, while Greach is announcing its impressive roster of speakers.&lt;/p>
&lt;p>But there’s an item of news dear to my heart this week, with the “go to production” message from Dierk about Groovy in Action, second edition! Many times the book has been revised to catch up with the latest and greatest of Groovy, and this new edition will bring you up-to-date with Groovy 2.4, so you can expect the official print and e-book final versions imminently!&lt;/p></description><content:encoded>
<![CDATA[<p>So what’s Groovy this week? The Groovy community is still celebrating the 4.5+ million downloads of Groovy last year, is drive testing the upcoming Gradle 2.3, while Greach is announcing its impressive roster of speakers.</p>
<p>But there’s an item of news dear to my heart this week, with the “go to production” message from Dierk about Groovy in Action, second edition! Many times the book has been revised to catch up with the latest and greatest of Groovy, and this new edition will bring you up-to-date with Groovy 2.4, so you can expect the official print and e-book final versions imminently!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/gradleware/status/562992965724172288">Gradle 2.3-rc-2</a> is available</li>
<li><a href="https://twitter.com/grailsflow/status/563301303464755201">GrailsFlow 1.5</a> released with Bootstrap 3</li>
<li>Joachim Baumann published the <a href="https://bintray.com/groovy/Distributions/Windows-Installer/groovy-2.4.0-installer/view">Windows installer for Groovy 2.4</a></li>
<li><a href="https://twitter.com/andrewreitz_/status/564908080593244160">Spock-Android 1.1</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://techblog.netflix.com/2015/02/nicobar-dynamic-scripting-library-for.html">Netflix open sources a new Groovy-based scripting library</a> for customizing their API UIs</li>
<li><a href="https://www.voxxed.com/blog/2015/02/speed-gradle-build-90-8-minutes/">Accelerate your Gradle build time</a> from 90 to 8 minutes</li>
<li>MrHaki&rsquo;s Spocklight: <a href="http://mrhaki.blogspot.fr/2015/02/spocklight-capture-and-assert-system.html">Capture and assert system output</a></li>
<li>Jochen Theodorou on <a href="http://blackdragsview.blogspot.fr/2015/02/getting-rid-of-compareto-for.html">getting rid of compareTo in favor of ==</a></li>
<li><a href="http://www.citytechinc.com/us/en/blog/2015/02/aem-groovy-console-new-features.html">New features for the venerable AEM</a> (Adobe CQ) Groovy Console</li>
<li><a href="http://blog.varunin.com/2015/01/developing-web-automation-framework.html">Developing a web automation framework using Geb</a> and Groovy by Varun Menon</li>
<li>The <a href="http://blog.jetbrains.com/idea/2015/02/intellij-idea-14-1-eap-is-available/">new EAP of IntelliJ IDEA features enhanced Gradle support</a> and allows you to use the Groovy-Eclipse compiler</li>
<li><a href="http://www.objectpartners.com/2015/02/03/groovy-closures-create-tidy-flexible-services/">Groovy Closures create tidy, flexible services</a> by Ben Brunk</li>
<li><a href="http://www.intelligrape.com/blog/significance-of-mappedby-in-grails-domain/">Significance of “mappedBy” in Grails domain classes</a> by Naman Gautam</li>
<li><a href="http://www.mscharhag.com/2015/02/creating-android-apps-with-groovy.html">Creating Android apps with Groovy 2.4</a> by Michael Scharhag</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="http://www.infoq.com/presentations/reactive-arch-grails">Reactive Oriented Architecture with Grails</a> by Steve Pember recorded at SpringOne2GX 2014</li>
<li>Cédric Champeau posted his slides from the MCE Conference in Poland about <a href="https://speakerdeck.com/melix/groovy-and-android-a-winning-pair-2">Groovy &amp; Android, a winning pair</a></li>
<li><a href="https://www.parleys.com/talk/groovy-light-java-8">Groovy in the light of Java 8</a> by Cédric Champeau at JavaOne 2014</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Dierk König announces that <a href="https://twitter.com/mittie/status/563825962606149632">all the content of the second edition of Groovy in Action is committed to production</a></li>
<li>Dierk König tells that the <a href="https://twitter.com/mittie/status/564461905281294336">second edition of Groovy in Action contains 20 chapters and 9 appendices</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Peter Ledbrook posted <a href="https://twitter.com/pledbrook/status/563371370974035968">Episode 6 of the Groovy podcast</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>A <a href="https://github.com/jdereg/n-cube">Groovy based rules engine</a>, decision table &amp; tree, templating engine</li>
<li>A <a href="https://github.com/tkruse/gpi">Groovy package installer</a>, &ldquo;gpi&rdquo;, from Thibault Kruse</li>
<li>Jacob Aae Mikkelsen publishes <a href="http://grydeske.net/news/show/82">Grails Diary</a> week 6</li>
<li>The list of <a href="https://github.com/grails3-plugins/">Grails plugins ready for Grails 3</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau summarizes the nice upward trend for <a href="https://twitter.com/CedricChampeau/status/562887140271726593">Groovy&rsquo;s yearly download numbers, reaching 4.5 millions in 2014</a></li>
<li>Craig Burke found a parallel with iOS&rsquo; &ldquo;there&rsquo;s an app for that&rdquo;, with &ldquo;<a href="https://twitter.com/craigburke1/status/562630876883083265">in Groovy, there&rsquo;s an AST transformation for that</a>&rdquo;</li>
<li>Craig Burke is working on a <a href="https://twitter.com/craigburke1/status/563413112238649345">PdfBox based document builder</a> with a friendly open source license</li>
<li>The <a href="https://twitter.com/gebframework/status/563270465452376064">book of Geb is being migrated to Asciidoctor</a> with automatically tested code snippets</li>
<li>The new <a href="https://twitter.com/gebframework/status/563041355816239104">Geb twitter account</a> reached hundred followers</li>
<li><a href="https://twitter.com/gvmtool/status/564691564665266177">Gradle 2.3-rc-3</a> available through GVM</li>
<li>You can <a href="https://twitter.com/nisafla/status/562303038912614400">fill a survey to help make Gradle tutorials better</a></li>
<li>Luke Daley announces a new <a href="https://twitter.com/ldaley/status/563113387169562624">Gradle plugin for running tests within Docker containers</a></li>
<li>Peter Ledbrook learned that <a href="https://twitter.com/pledbrook/status/563434449191579648">Groovy&rsquo;s type checker can infer the component type of list literals</a></li>
<li>Clint Liddick says <a href="https://twitter.com/ClintLiddick/status/563817158720118784">IntelliJ IDEA&rsquo;s Groovy console is way nicer than an hypothetic Java REPL</a></li>
<li>You can watch <a href="https://twitter.com/grooscript/status/565265212761464832">GrooScript in action</a> at the Greach conference</li>
<li>A last final MEAP of Groovy in Action second edition will be out soon, says Dierk König</li>
<li>Sean Gilligan works on a <a href="https://twitter.com/jsr354/status/564420791224508416">Groovy support for JSR-354 / Money</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/greachconf/status/563296040539811840">Greach tweets about its roster of speakers</a></li>
<li><a href="https://twitter.com/greachconf/status/563294745183539201">More speakers for the Greach</a> conference</li>
<li>The <a href="https://twitter.com/greachconf/status/565084022733160448">Greach conference agenda</a> is online</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #56</title><link>https://glaforge.dev/posts/2015/02/03/groovy-weekly-56/</link><pubDate>Tue, 03 Feb 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/02/03/groovy-weekly-56/</guid><description>&lt;p>Congrats to the GrooScript team with the &lt;a href="http://grooscript.org/announcement.html">GrooScript 1.0&lt;/a> release! With GrooScript, if you will, you can have a full Groovy experience, from the build (Gradle), backend (Groovy, Grails, Ratpack), mobile (Groovy on Android), up to the front-end web layer with Groovy translated into JavaScript!&lt;/p>
&lt;p>Another impressive and widely acclaimed release is the &lt;a href="https://github.com/grails/grails-core/releases/tag/v3.0.0.M1">first milestone of Grails 3&lt;/a>! You can have a look at the &lt;a href="http://grails.github.io/grails-doc/3.0.x/guide/introduction.html#whatsNew">novelties in Grails 3&lt;/a>, and watch a &lt;a href="https://www.youtube.com/watch?v=aro3_RZqgtU">screencast&lt;/a> about it.&lt;/p></description><content:encoded>
<![CDATA[<p>Congrats to the GrooScript team with the <a href="http://grooscript.org/announcement.html">GrooScript 1.0</a> release! With GrooScript, if you will, you can have a full Groovy experience, from the build (Gradle), backend (Groovy, Grails, Ratpack), mobile (Groovy on Android), up to the front-end web layer with Groovy translated into JavaScript!</p>
<p>Another impressive and widely acclaimed release is the <a href="https://github.com/grails/grails-core/releases/tag/v3.0.0.M1">first milestone of Grails 3</a>! You can have a look at the <a href="http://grails.github.io/grails-doc/3.0.x/guide/introduction.html#whatsNew">novelties in Grails 3</a>, and watch a <a href="https://www.youtube.com/watch?v=aro3_RZqgtU">screencast</a> about it.</p>
<p>The Ratpack team has also been pretty busy with <a href="http://www.ratpack.io/versions/0.9.13">Ratpack 0.9.13</a>, with over 17k lines of code changed in this release!</p>
<p>Guillaume Laforge also announced that <a href="https://twitter.com/glaforge/status/562590782084112384">Groovy was downloaded over 4 million times from Maven Central</a>, without even counting the downloads of the binary distributions from Codehaus or Bintray. In total, Groovy will likely be beyond 5 million times in total, compared with 3 millions the past year.</p>
<h2 id="releases">Releases</h2>
<ul>
<li>The <a href="https://github.com/grails/grails-core/releases/tag/v3.0.0.M1">first milestone release of Grails 3</a> is out!</li>
<li><a href="http://grooscript.org/announcement.html">GrooScript 1.0</a> released, the Groovy to JavaScript transpiler</li>
<li><a href="http://www.ratpack.io/versions/0.9.13">Ratpack 0.9.13</a> released, a &ldquo;big one&rdquo;!</li>
<li><a href="https://twitter.com/davydotcom/status/560503261137231872">Grails asset-pipeline plugin v2.1.1</a></li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>InfoQ covers the <a href="http://www.infoq.com/news/2015/01/groovy14-android">Groovy 2.4 release and highlights the Android support</a></li>
<li>MrHaki&rsquo;s Groovy Goodness:
<ul>
<li><a href="http://mrhaki.blogspot.fr/2015/01/groovy-goodness-getting-all-but-last.html">getting all but the last element in a collection with the init method</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/01/groovy-goodness-getting-indices-of.html">getting the indices of a collection</a></li>
<li><a href="http://mrhaki.blogspot.fr/2015/01/groovy-goodness-pop-and-push-items-in.html">pop and push items in a list</a></li>
</ul>
</li>
<li>Mariano Gonzalez wrote about <a href="http://blogs.mulesoft.org/mule-3-6-polyglot-programming/">polyglot programming in Mule</a>, with the scripting pack now part of the distribution, and showing an example with Groovy</li>
<li><a href="http://davydotcom.com/blog/2015-01-29-grails-3-0-0-m1-asset-pipeline-tips-tricks">Grails 3.0.0.M1 Asset-Pipeline tips and tricks</a> by David Estes</li>
<li>Grails Clean Code: <a href="http://www.intelligrape.com/blog/grails-clean-code-configure-codenarc-plugin/">Configure Codenarc plugin</a> by Uday Pratap Singh</li>
<li>Building a <a href="https://www.accelebrate.com/blog/building-geolocation-web-application-groovy-grails/">geolocation web application with Groovy and Grails</a> by Ken Kousen</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>The <a href="http://www.infoq.com/presentations/paypal-api-evolution">PayPal API platform is using Groovy as its orchestration language</a>, as mentioned in this presentation given at QCon San Francisco 2014</li>
<li>SpringOne2GX 2014 Replay
<ul>
<li><a href="https://spring.io/blog/2015/01/26/springone2gx-2014-replay-groovy-for-system-administrators">Groovy for system administrators</a> by Dan Woods</li>
<li><a href="http://www.infoq.com/presentations/ratpack-2014">The Ratpack web framework</a> by Dan Woods</li>
<li><a href="http://www.infoq.com/presentations/clustering-terracota-quartz-grails">Web clustering, integration with Terracotta, BigMemory, Quartz &amp; Grails</a> by Ryan Vanderwerf</li>
<li><a href="http://www.infoq.com/presentations/grails-testing-2014">Testing Grails</a> by Ken Kousen</li>
<li><a href="http://www.infoq.com/presentations/cd-gradle-jenkins-2014">Building a Continuous Delivery pipeline with Gradle and Jenkins</a> by Peter Niederwieser</li>
</ul>
</li>
<li>Building a <a href="https://www.accelebrate.com/blog/building-geolocation-web-application-groovy-grails/">geolocation web application with Groovy and Grails</a> by Ken Kousen</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>A <a href="https://twitter.com/grailsframework/status/560752646093832192">screencast preview of Grails 3</a> by Graeme Rocher for the M1 release</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/81">Grails Diary</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Guillaume Laforge announced <a href="https://twitter.com/glaforge/status/562590782084112384">Groovy being downloaded over 4 million times in 2014 from Maven Central</a>. With the Codehaus distribution figures, it might mean beyond 5 million total downloads!</li>
<li>Cédric Champeau realizes <a href="https://twitter.com/cedricchampeau/status/560837091764887552">how huge Groovy is when one has to write its documentation</a>! Or when you notice that Groovy in Action (second edition) is over 1000 pages!</li>
<li><a href="https://twitter.com/mg6maciej/status/560865008830382081">Testing Android apps with Espresso is very expressive in Groovy</a> remarks Maciej Górski</li>
<li>Victor Trakhtenberg needs some <a href="https://twitter.com/victortr75/status/560393895658283009">votes for his Groovy talk</a> on confessions of a Java developer that fell in love with Groovy</li>
<li>Dierk König says that <a href="https://twitter.com/mittie/status/562570994918100993">Groovy in Action, second edition, contains over 600 code listings</a></li>
<li>John Engelman counted <a href="https://twitter.com/johnrengelman/status/562081630677127168">17k code line changes in Ratpack 0.9.13</a></li>
<li>At FOSDEM, the <a href="https://twitter.com/thetaph1/status/561891670082732032">Oracle team mentioned Groovy&rsquo;s contributions to debugging JDK 8</a>, thanks to Cédric Champeau&rsquo;s bug reports</li>
<li>You can get grooscript.js from from NPM and Bower</li>
<li>Marco Vermeulen is <a href="https://twitter.com/marc0der/status/561993267819323393">impressed with Grails 3</a> and how the Grails team outdid themselves!</li>
<li><a href="https://twitter.com/gvmtool/status/560920057916948480">Grails 3.0 milestone 1</a> is available on GVM</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Philippe Charrière implemented his first <a href="https://github.com/k33g/kiss-groovy/blob/master/app/main.groovy#L16">http server framework with Groovy</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/marc0der/status/562142093267959808">Marco Vermeulen will be speaking at Greach</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #55</title><link>https://glaforge.dev/posts/2015/01/27/groovy-weekly-55/</link><pubDate>Tue, 27 Jan 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/01/27/groovy-weekly-55/</guid><description>&lt;p>This week is a bit more positive than the previous with the announcement of the &lt;a href="https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/">release of Groovy 2.4&lt;/a>!&lt;/p>
&lt;p>But some interesting and thoughtful pieces have also been written about last week’s sad news about Pivotal divesting in Groovy and Grails.&lt;/p>
&lt;p>In spite of this, &lt;a href="https://twitter.com/springone2gx/status/559903903756861440">SpringOne2GX 2015 has been announced with 4 Groovy and Grails tracks&lt;/a>, in Washington DC, mid-September.&lt;/p>
&lt;p>Last but not least, the &lt;a href="https://twitter.com/mittie/status/558011051313491969">second edition of Groovy in Action is going into production&lt;/a>!&lt;/p></description><content:encoded>
<![CDATA[<p>This week is a bit more positive than the previous with the announcement of the <a href="https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/">release of Groovy 2.4</a>!</p>
<p>But some interesting and thoughtful pieces have also been written about last week’s sad news about Pivotal divesting in Groovy and Grails.</p>
<p>In spite of this, <a href="https://twitter.com/springone2gx/status/559903903756861440">SpringOne2GX 2015 has been announced with 4 Groovy and Grails tracks</a>, in Washington DC, mid-September.</p>
<p>Last but not least, the <a href="https://twitter.com/mittie/status/558011051313491969">second edition of Groovy in Action is going into production</a>!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/">Groovy 2.4</a> released</li>
<li><a href="http://groovy.329449.n5.nabble.com/GMaven-1-3-Released-td5722239.html">GMaven Plus 1.3</a> released</li>
<li><a href="https://twitter.com/arasthel92/status/559659062309449728">SwissKnife 1.2.2</a> released</li>
<li><a href="https://twitter.com/grooscript/status/557999421691269120">Second release candidate for GrooScript 1.0</a></li>
<li><a href="https://twitter.com/grooscript/status/558000095070015488">GrooScript Gradle plugin 0.10</a> released</li>
<li><a href="https://twitter.com/gradleware/status/560099446508519426">Gradle 2.3-rc-1</a> available for testing</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Voxxed notes that <a href="https://www.voxxed.com/blog/2015/01/groovy-verse-back-business-2-4-release/">Groovy is back to business with its 2.4 release</a></li>
<li>Object Partners writes <a href="http://www.objectpartners.com/2015/01/23/keep-calm-groovy-on/">Keep Calm and Groovy on</a>!</li>
<li>Henrique Lobo Weissmann compares <a href="http://www.itexto.com.br/devkico/en/?p=69">Groovy and Grails to &ldquo;bridges&rdquo; technology</a></li>
<li>Dan Vega shares his <a href="http://therealdanvega.com/blog/2015/01/20/pivotal-drops-groovy-grails-sponsorship">thoughtful views on the Groovy &amp; Grails loss of funding</a> from Pivotal</li>
<li>Peter Ledbrook shares his <a href="http://blog.cacoethes.co.uk/groovyandgrails/the-future-of-groovy">thoughts on the future of Groovy</a></li>
<li>TheRegister covers the <a href="http://www.theregister.co.uk/2015/01/22/open_source_java_grails_and_groovy_as_pivotal_pulls_out/">loss of sponsorship from Pivotal of the Groovy and Grails projects</a></li>
<li>Dustin Marx writes about the <a href="http://java.dzone.com/articles/total-bummer-pivotal-drops">total bummer that Pivotal drops Groovy</a></li>
<li>Koshuke Kawaguchi shares his view that the <a href="http://kohsuke.org/2015/01/20/groovy-project-should-have-a-clear-governance-structure/">Groovy project should have a clear governance structure</a></li>
<li>Mike Miller detects a <a href="http://programmingitch.blogspot.dk/2015/01/i-detect-disturbance-in-groovy-grails.html">disturbance in the (Groovy &amp; Grails) force</a></li>
<li>Groovy and Grails, <a href="http://www.intelligrape.com/blog/groovy-and-grails-gr8-without-pivotal/">GR8 without Pivotal</a> writes IntelliGrape&rsquo;s Himanshu Seth</li>
<li>Craig Burke worked on a <a href="http://www.craigburke.com/2015/01/22/groovy-document-builder.html">document builder to create Word and PDF documents in Groovy</a></li>
<li>The <a href="http://beta.groovy-lang.org/releasenotes/groovy-2.4.html">Groovy 2.4 release notes</a></li>
<li>The <a href="http://beta.groovy-lang.org/changelog-2.4.0.html">Groovy 2.4 changelog</a> now up on the new Groovy website</li>
<li>A Quora question: &ldquo;<a href="https://www.quora.com/Pivotal-is-no-longer-sponsoring-Grails-I-have-a-Grails-project-in-the-works-What-should-I-do">I&rsquo;ve started a Grails project, what should I do?</a>&rdquo;</li>
<li>Another Quora question: <a href="https://www.quora.com/Why-is-Pivotal-ending-the-sponsorship-of-Groovy-and-Grails">why is Pivotal ending sponsorship of Groovy and Grails</a>?</li>
<li>MrHaki&rsquo;s Groovy goodness: <a href="http://mrhaki.blogspot.fr/2015/01/groovy-goodness-take-or-drop-last-items.html">take or drop last items of a collection</a></li>
<li>On <a href="http://blog.robustastudio.com/mobile-development/android/building-multiple-editions-of-android-app-gradle/">building multiple editions of an Android application with Gradle</a> by Hassan Ibraheem</li>
<li><a href="https://www.voxxed.com/blog/2015/01/jvm-metrics-grails-awesomeness/">JVM metrics + Grails = awesomeness</a>, by Erwan Arzur</li>
<li>Yamila Moreno comes back on her Groovy 101 series with a post on <a href="http://moduslaborandi.net/groovy-101-gradle-travis-and-jenkins/">Groovy, GVM, Gradle, Travis and Jenkins</a></li>
<li>Using the <a href="http://www.perfectlearn.com/2015/01/how-i-used-the-cia-world-factbook-to-test-my-product/">CIA world fact book with Groovy</a></li>
<li>Let’s talk commitment: <a href="http://blog.ship.io/2015/01/23/lets-talk-commitment-gradle-and-version-control/">Gradle and version control</a>, by Tim Rosenblatt</li>
</ul>
<h2 id="podcast">Podcast</h2>
<ul>
<li>The Groovy podcast <a href="https://twitter.com/dailygrailstip/status/557943849251643393">interviewed Graeme Rocher and Guillaume Laforge</a></li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Project lead <a href="http://jaxenter.com/grails-future-113958.html">Graeme Rocher on the future of Grails</a> on JaxEnter</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>A <a href="http://www.infoq.com/presentations/grails-3-preview">Grails 3.0 preview</a> by Graeme Rocher, recorded at SpringOne2GX 2014</li>
<li>The <a href="http://www.infoq.com/presentations/grails-introduction">quest for the holy Grails</a> by Ken Kousen, recorded at SpringOne2GX 2014</li>
<li><a href="http://www.infoq.com/presentations/grails-security-auth">Securing your Grails application, beyond authentication and authorization</a>, by Colin Harrington, recorded at SpringOne2GX 2014</li>
<li>David Carr&rsquo;s latest <a href="http://www.slideshare.net/davidmc24/intro-to-ratpack-cdjdn-2015">Ratpack introduction presentation</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>You can <a href="http://www.dzone.com/links/groovy_24_released_with_android_support.html">upvote this DZone news item about the release of Groovy 2.4</a></li>
<li>A fun little <a href="http://grooscript.org/demo/stars.html">demo of Groovy in the browser with GrooScript</a></li>
<li>Alcatel-Lucent&rsquo;s <a href="http://ecosystem.cloud-band.com/wp-content/uploads/2013/09/CloudBand-cPaas-Technical-Overview-1.6.pdf">PaaS solution leverages Groovy as its orchestration language</a></li>
<li>Andy Clement announces <a href="https://twitter.com/andy_clement/status/559061751736455169">Groovy Eclipse snapshots for Eclipse 4.4 have moved to Groovy 2.4</a></li>
<li><a href="https://github.com/ssadedin/graxxia">Graxxia, a mathematics library for Groovy</a>, wrapping commons-math, OpenCSV, GroovyCSV</li>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/80">Grails Diary</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Dierk König is announcing that <a href="https://twitter.com/mittie/status/559841646654029824">Groovy in Action eventually goes to production</a>!</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>In <a href="https://gist.github.com/rmannibucau/e14324cdfc278ad388f1">24 lines of Groovy code to develop a JAX-RS service with OpenEJB</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>A Twitter <a href="https://twitter.com/alvaro_sanchez/status/559635311329026048">poll considers Google, Red Hat, Netflix and Gradleware as best sponsors</a> for the Groovy and Grails projects</li>
<li>Gradleware initiated a <a href="https://twitter.com/hashtag/WhyGradle?src=hash">twitter hashtag on &ldquo;why Gradle&rdquo;</a>, to which many contributed. Why do you use Gradle?</li>
<li>Dierk remarks that <a href="https://twitter.com/mittie/status/558011051313491969">Groovy 2.4 is as a compelling Android alternative to Java</a></li>
<li>Sergey Tselovalnikof noticed a <a href="https://twitter.com/SerCeMan/status/557960043380109312">funny little banner in Groovy&rsquo;s build</a>!</li>
<li><a href="https://twitter.com/gvmtool/status/557941663319203841">Groovy 2.4.0</a> available through GVM</li>
<li><a href="https://twitter.com/gvmtool/status/560101012623880192">Gradle 2.3-rc-1</a> available through GVM</li>
<li>Peter Ledbrook is looking forward to seeing the impact of Gradle&rsquo;s new configuration model</li>
<li>Cédric Champeau remarks that <a href="https://twitter.com/cedricchampeau/status/557822571543273473">positive comments outnumber FUD by an order of magniture</a></li>
<li>Cédric Champeau announces the <a href="https://twitter.com/CedricChampeau/status/558197359197560832">changes in versions in the Github branches</a> following up the release of Groovy 2.4</li>
<li><a href="https://twitter.com/mittie/status/558252300775731200">Performance improvements against Groovy 2.4</a> when Dierk König is running the self testing listings of Groovy in Action</li>
<li>Iván López thinks <a href="https://twitter.com/ilopmar/status/558274612287778816">Google should sponsor the Groovy and Grails projects</a>, especially now that Groovy 2.4 supports the Android platform</li>
<li>Florent Biville thinks <a href="https://twitter.com/fbiville/status/558977065710800896">since Groovy supports Android, Google should hire the Groovy and Grails teams</a></li>
<li>GradleWare offers two <a href="https://twitter.com/tomaslin/status/558806680599670784">free O&rsquo;Reilly e-books on Gradle</a></li>
<li>Erik Pragt shares a glimpse into the <a href="https://twitter.com/epragt/status/558983812655640576">new Asciidoctor-based Geb documentation</a></li>
<li>The <a href="http://rundis.github.io/blog/2015/gr_lt_status_jan2015.html">Groovy Light Table client is not dead</a> says Magnus Rundberget</li>
<li>Marco Vermeulen is making progress on the <a href="https://twitter.com/marc0der/status/559130196855771136">GVM security access for vendors</a></li>
<li>Antonio Goncalves tweets that a Maven <a href="https://twitter.com/agoncal/status/560001785654685696">pom.xml with the GMaven plugin and hundreds lines of Groovy code is probably by someone who wanted to use Gradle</a> but couldn&rsquo;t</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/greachconf/status/559035001225101313">Greach organizers have received lots of great talk proposals</a></li>
<li><a href="https://twitter.com/springone2gx/status/559903903756861440">SpringOne2GX 2015 has been announced</a>, with 4 Groovy and Grails tracks, in Washington DC on Sept 14th-17th</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.4 released</title><link>https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/</link><pubDate>Wed, 21 Jan 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/01/21/groovy-2-4-released/</guid><description>&lt;p>The Groovy team is happy to announce the release of Groovy 2.4.0!&lt;/p>
&lt;p>The big highlight of this release is the Android support, which allows developers to write Android applications fully using Groovy, with much less boilerplate code than raw Java.&lt;/p>
&lt;p>The team also focused on various improvements in terms of performance, smaller bytecode generation, or memory consumption.&lt;/p>
&lt;p>Other interesting aspects worth noticing in this release are:&lt;/p>
&lt;ul>
&lt;li>traits can use the &lt;code>@SelfType&lt;/code> annotation with static type checking enabled to restrict to what classes traits can be applied&lt;/li>
&lt;li>GDK methods improvements&lt;/li>
&lt;li>some refinements to existing AST transformations&lt;/li>
&lt;li>further Groovysh improvements as well.&lt;/li>
&lt;/ul>
&lt;p>Please have a look at the &lt;a href="http://docs.codehaus.org/display/GROOVY/Groovy+2.4+release+notes">full release notes for Groovy 2.4&lt;/a> to know more about the new features and all the interesting tickets closed.&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy team is happy to announce the release of Groovy 2.4.0!</p>
<p>The big highlight of this release is the Android support, which allows developers to write Android applications fully using Groovy, with much less boilerplate code than raw Java.</p>
<p>The team also focused on various improvements in terms of performance, smaller bytecode generation, or memory consumption.</p>
<p>Other interesting aspects worth noticing in this release are:</p>
<ul>
<li>traits can use the <code>@SelfType</code> annotation with static type checking enabled to restrict to what classes traits can be applied</li>
<li>GDK methods improvements</li>
<li>some refinements to existing AST transformations</li>
<li>further Groovysh improvements as well.</li>
</ul>
<p>Please have a look at the <a href="http://docs.codehaus.org/display/GROOVY/Groovy+2.4+release+notes">full release notes for Groovy 2.4</a> to know more about the new features and all the interesting tickets closed.</p>
<p>You can have a look at the <a href="http://beta.groovy-lang.org/changelog-2.4.0.html">2.4 changelog</a> on the new Groovy website too.</p>
<p>And then, just go <a href="http://beta.groovy-lang.org/download.html">grab this release</a> while it&rsquo;s hot!</p>
<p>Thanks a lot to all those who contributed to this release, whether through bug reports, but also with documentation or code contributions through pull requests. All your help is warmly welcome!</p>
<p>Your support of Groovy and its ecosystem is what makes Groovy so strong, and what will allow it to continue making us all more productive for the next decade!</p>
<p>Keep on groovy&rsquo;ing!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #54</title><link>https://glaforge.dev/posts/2015/01/20/groovy-weekly-54/</link><pubDate>Tue, 20 Jan 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/01/20/groovy-weekly-54/</guid><description>&lt;p>This week’s edition of Groovy Weekly is a bit peculiar, as the big news of the week is &lt;a href="http://blog.pivotal.io/pivotal/news-2/groovy-2-4-and-grails-3-0-to-be-last-major-releases-under-pivotal-sponsorship">Pivotal’s announcement that it will stop funding the Groovy and Grails&lt;/a> projects.&lt;/p>
&lt;p>As a result those popular Open Source projects are now seeking a new home!&lt;/p>
&lt;p>You can read &lt;a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">Guillaume Laforge’s&lt;/a> and &lt;a href="http://grails.io/post/108534902333/the-future-of-groovy-grails-sponsorship">Graeme Rocher’s&lt;/a> announcements to hear a bit more about the story. And if your company is interested in further funding the projects, please don’t hesitate to &lt;a href="mailto:sponsorship@groovy-lang.org">get in touch&lt;/a>!&lt;/p></description><content:encoded>
<![CDATA[<p>This week’s edition of Groovy Weekly is a bit peculiar, as the big news of the week is <a href="http://blog.pivotal.io/pivotal/news-2/groovy-2-4-and-grails-3-0-to-be-last-major-releases-under-pivotal-sponsorship">Pivotal’s announcement that it will stop funding the Groovy and Grails</a> projects.</p>
<p>As a result those popular Open Source projects are now seeking a new home!</p>
<p>You can read <a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">Guillaume Laforge’s</a> and <a href="http://grails.io/post/108534902333/the-future-of-groovy-grails-sponsorship">Graeme Rocher’s</a> announcements to hear a bit more about the story. And if your company is interested in further funding the projects, please don’t hesitate to <a href="mailto:sponsorship@groovy-lang.org">get in touch</a>!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/cedricchampeau/status/555014971814576129">Gradle Groovy Android plugin 0.3.5</a> updated with new coordinates</li>
<li>The axion-release-plugin hits 1.0, a <a href="http://allegrotech.io/axion-release-plugin.html">&ldquo;release&rdquo; plugin for Gradle</a></li>
<li><a href="https://twitter.com/bsideup/status/556820732576272385">Groovy-Macro-Methods 0.3.0</a> with huge optimizations</li>
<li><a href="https://twitter.com/springcentral/status/557245887051603969">Groovy Grails Tool Suite 3.6.3 SR1</a> released fixing a security vulnerability</li>
<li><a href="https://twitter.com/grainframework/status/557452579819360257">Grain 0.6.3</a> released with updated Asciidoctor</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://blog.pivotal.io/pivotal/news-2/groovy-2-4-and-grails-3-0-to-be-last-major-releases-under-pivotal-sponsorship">Pivotal announces the end of the funding of the Groovy and Grails</a> projects</li>
<li><a href="https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/">Guillaume Laforge’s announcement</a> about Pivotal’s end of sponsorship of the Groovy and Grails projects</li>
<li><a href="http://grails.io/post/108534902333/the-future-of-groovy-grails-sponsorship">Graeme Rocher’s announcement</a> regarding the end of the funding of the Groovy and Grails projects by Pivotal</li>
<li><a href="https://docs.google.com/a/pivotal.io/document/d/1R_bQv_8JSLOEzVzPdqOISF0guGtcUPE6vFBeAuxtcG4/edit">Pivotal&rsquo;s FAQ on the end of sponsorship</a> of the Groovy and Grails projects</li>
<li>Cédric Champeau wrote a guest blog post on TeamCity&rsquo;s blog about <a href="http://blog.jetbrains.com/teamcity/2015/01/how-groovy-uses-teamcity/">how Groovy uses TeamCity</a></li>
<li><a href="http://melix.github.io/blog/2015/01/for-hire.html">Cédric Champeau of Groovy fame would love to continue to work on Groovy full time, but he&rsquo;s soon gonna be for hire otherwise</a>, catch him if you get a chance!</li>
<li>Ken Kousen’s lovely post on <a href="https://kousenit.wordpress.com/2015/01/19/groovygrails-pivotal-opportunity/">why Groovy and Grails community matters</a>, opportunities ahead!</li>
<li>Peter Ledbrook details <a href="http://blog.cacoethes.co.uk/software/why-gradle">why you should use Gradle</a></li>
<li><a href="http://redmonk.com/sogrady/2015/01/14/language-rankings-1-15/">Groovy listed in the top 20 of RedMonk&rsquo;s programming language rankings</a> of January 2015</li>
<li><a href="http://www.objectpartners.com/2015/01/13/reset-your-h2-database-for-a-clean-state-between-functional-tests/">Reset your H2 database for a clean state between functional tests</a> with the help of Spock and Groovy traits</li>
<li>A lively imaginary <a href="http://blog.cleancoder.com/uncle-bob/2015/01/08/InterfaceConsideredHarmful.html">discussion on interfaces, abstract classes, multiple inheritance</a> by Uncle Bob, which may make you think Groovy traits might be the answer</li>
<li>The <a href="http://code.tutsplus.com/tutorials/the-ins-and-outs-of-gradle--cms-22978">ins and outs of Gradle</a>, by Jessica Thornsby, from an Android perspective</li>
<li>Voxxed published a master-post to <a href="https://www.voxxed.com/blog/2015/01/thinking-about-picking-up-a-new-jvm-language-a-masterpost-to-guide-java-devs/">guide Java developers choosing a new JVM language</a>, including Groovy in the list</li>
<li><a href="https://www.virag.si/2015/01/publishing-gradle-android-library-to-jcenter/">Publishing Gradle Android library to JCenter repository</a> by Jernej Virag</li>
<li>Anton Arhipov thinks what’s happening could be a <a href="http://arhipov.blogspot.fr/2015/01/g.html">new beginning for Groovy and Grails</a></li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Victor Grazi <a href="http://www.infoq.com/news/2015/01/Pivotal-Pulls-Groovy-Grails-Fund">interviewed Guillaume Laforge on Pivotal&rsquo;s decision</a> with regards to Groovy and Grails</li>
<li>Guillaume Laforge interviewed by Voxxed on the “<a href="https://www.voxxed.com/blog/2015/01/sad-odd-decision-pivotal-set-groovy-adrift/">sad and odd decision by Pivotal to set Groovy adrift</a>”</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>InfoQ SpringOne2GX coverage:
<ul>
<li><a href="http://www.infoq.com/presentations/groovy-2-3-api-design">Rethinking API design with traits</a>, by Cédric Champeau</li>
<li><a href="http://www.infoq.com/presentations/groovy-test-java-spock">Testing Java, Groovy, Spring and web applications with Spock</a>, by Peter Niederwieser</li>
<li><a href="http://www.infoq.com/presentations/bigdata-storm-groovy">Scalable Big Data stream processing with Storm and Groovy</a>, by Eugene Dvorkin</li>
</ul>
</li>
<li>SpringOne2GX 2014 replay from the Spring.IO blog
<ul>
<li><a href="https://spring.io/blog/2015/01/09/springone2gx-2014-replay-how-to-get-groovy-with-java-8">How to get Groovy with Java 8</a> by Peter Ledbrook</li>
<li><a href="https://spring.io/blog/2015/01/08/springone2gx-2014-replay-android-and-groovy-a-winning-pair">Android and Groovy, a winning pair?</a> by Cédric Champeau</li>
<li><a href="https://spring.io/blog/2015/01/08/springone2gx-2014-replay-experiences-using-grails-in-a-microservice-architecture">Experiences using Grails in a microservice architecture</a> by Jeff Beck</li>
<li>Dan Woods <a href="http://fr.slideshare.net/danveloper/ratpack-web-framework-from-oreilly-webcast-20150113">introduction to Ratpack</a> in this O&rsquo;Reilly webcast</li>
</ul>
</li>
<li>Slides of the Grails Centro meetup on the <a href="https://www.slideshare.net/fullscreen/bobdobbes/silicon-valley-grails/1">Grails API toolkit</a> by Owen Rubel</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>All the <a href="https://github.com/grails/grails-core/releases">Grails release notes are now available on Github</a></li>
<li><a href="http://www.eclecticlogic.com/pedal-loader/">Pedal-Loader is a Groovy DSL for data loading</a> and an attractive substitute to db-unit</li>
<li>A work-in-progress of the <a href="http://grails.github.io/grails-static-website/">future Grails static web site</a>, forked from Groovy&rsquo;s new website</li>
<li>The <a href="http://grooscript.org/doc.html">GrooScript&rsquo;s documentation</a> is almost finished, any feedback?</li>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/79">Grails Diary</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>A <a href="https://gist.github.com/chiquitinxx/6ef000456732aa9bd7fb">single page interface with Groovy traits, templates, builders and GrooScript</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Stephan Janssen, founder of Devoxx predicts that <a href="https://twitter.com/Stephan007/status/557459062221455364">2015 will be the year Red Hat hires the Groovy and Grails teams</a> and embraces their communities</li>
<li>Latest <a href="https://twitter.com/cedricchampeau/status/555015335313932289">Gradle Groovy Android plugin available on Bintray and the Gradle plugin portal</a></li>
<li>Guillaume Laforge&rsquo;s <a href="https://twitter.com/glaforge/status/556027163494084608">duck typing joke in the age of quantum computing</a></li>
<li>Rohit Kelapure answers yes to the question of <a href="https://twitter.com/rkela/status/555079299074916353">pushing Ratpack apps on Cloud Foundry out of the box</a></li>
<li><a href="https://twitter.com/grooscript/status/556466614259154944">GrooScript is aiming a 1.0 release</a> by the end of January</li>
<li>Arnaud Héritier is supporting Groovy saying &ldquo;<a href="https://twitter.com/aheritier/status/557138343914655744">Je Suis Groovy</a>&rdquo;, in echo to recent tragic terrorists attacks in France</li>
<li>Jeff Brown reminds us that the <a href="https://twitter.com/jeffscottbrown/status/557196649916022784">main focus for the Groovy and Grails teams are to secure the technologies&rsquo; future</a></li>
<li>Burt Beckwith suggesting <a href="https://twitter.com/burtbeckwith/status/557220977437081600">being fast to get a chance to snap the talented Groovy and Grails teams</a>!</li>
<li>Andy Wilkinson reminds us <a href="https://twitter.com/ankinson/status/557457888160280576">some developers believe that software costs nothing to produce</a></li>
<li>Dean Iverson notes that the <a href="https://twitter.com/deanriverson/status/557302834815700992">Pivotal&rsquo;s divesting doesn&rsquo;t make Groovy any less useful</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Jochen Theodorou will be the Groovy keynote speaker of Greach 2015</li>
<li><a href="https://twitter.com/greachconf/status/556021832747192321">Salenda is a new gold sponsor of the Greach</a> conference</li>
<li><a href="https://twitter.com/greachconf/status/556031871260192768">Burt Beckwith will be speaking at the Greach</a> conference</li>
<li>GR8Ladies founder <a href="https://twitter.com/greachconf/status/554964855027818497">Jane Stratter will be speaking at Greach</a></li>
<li>Still <a href="https://twitter.com/gr8conf/status/556244984177766400">curious about the GR8Conf conference</a>? Check the videos!</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>The Groovy project is looking for a new home</title><link>https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/</link><pubDate>Mon, 19 Jan 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/01/19/the-groovy-project-is-looking-for-a-new-home/</guid><description>&lt;p>Pivotal just announced it’s decision &lt;a href="http://blog.pivotal.io/pivotal/news-2/groovy-2-4-and-grails-3-0-to-be-last-major-releases-under-pivotal-sponsorship">to stop sponsoring and funding the development of the popular Groovy and Grails Open Source projects&lt;/a>. As a result, both Groovy and Grails are looking for new sponsors willing to further help develop the projects full steam!&lt;/p>
&lt;p>The &lt;a href="http://groovy-lang.org">Groovy programming language&lt;/a> has been around for a while for more than 11 years. During that time, it has nicely evolved from a side hobby project to the very mature and successful alternative language it is today, used by Fortune 500 companies throughout the world, in various projects and contexts.&lt;/p></description><content:encoded>
<![CDATA[<p>Pivotal just announced it’s decision <a href="http://blog.pivotal.io/pivotal/news-2/groovy-2-4-and-grails-3-0-to-be-last-major-releases-under-pivotal-sponsorship">to stop sponsoring and funding the development of the popular Groovy and Grails Open Source projects</a>. As a result, both Groovy and Grails are looking for new sponsors willing to further help develop the projects full steam!</p>
<p>The <a href="http://groovy-lang.org">Groovy programming language</a> has been around for a while for more than 11 years. During that time, it has nicely evolved from a side hobby project to the very mature and successful alternative language it is today, used by Fortune 500 companies throughout the world, in various projects and contexts.</p>
<p>With 1.7 million downloads in 2012, 3 million in 2013, and well over 4 million in 2014 (definitive numbers still need to be calculated), Groovy is leading the pack of the JVM language ecosystem, and continues seeing positive growth.</p>
<p>There are many ideas the Groovy team wants to develop further, features we want to bring to life, improvements we want to make, to keep Groovy always ahead of the curve, to help you developers be productive on the JVM platform. For that, we’ve been thankful for having had a handful of us able to work full time on the project, and we’re looking forward continuing to do so under a new umbrella.</p>
<p>The Groovy community has always been a key driver for the language, providing feedback, bug reports, contributions big and small, and we hope that you will help us find a solution to make Groovy shine as bright as ever.</p>
<p>Of course, we’re going to continue to develop Groovy, open it to new horizons like we did for the Android platform, implement new features, fix bugs, increase performance, complete the new documentation, launch the new website, and more. Your contributions will obviously be more than welcome to sustain the project’s pace. We’re looking forward to working with you all to push Groovy forward!</p>
<p>If your company is interested in discussing funding of the project, and employing members of the Groovy team, please don’t hesitate to contact us directly (<a href="mailto:sponsorship@groovy-lang.org">sponsorship@groovy-lang.org</a>). Thanks in advance for your help, and keep on groovy’ing!</p>
<p><em>Update:</em> <a href="http://grails.io/post/108534902333/the-future-of-groovy-grails-sponsorship">Graeme&rsquo;s blog post</a> about the announcement</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #53</title><link>https://glaforge.dev/posts/2015/01/13/groovy-weekly-53/</link><pubDate>Tue, 13 Jan 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/01/13/groovy-weekly-53/</guid><description>&lt;p>The Groovy development team released the second release candidate of Groovy 2.4, please be sure to check your applications with this version and report any issue you may be finding, so that we can all ensure 2.4 is a rock-solid release.&lt;/p>
&lt;p>Note the publication of MrHaki’s &lt;a href="http://mrhaki.blogspot.fr/2015/01/gradle-goodness-notebook-is-published.html">Gradle Goodness Notebook&lt;/a>! If you’re using Gradle, this is worth the investment to have that resource handy for mastering your build automation.&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://groovy.329449.n5.nabble.com/ANN-Second-release-candidate-for-Groovy-2-4-td5722110.html">Groovy 2.4 second release candidate&lt;/a>, please help testing it!&lt;/li>
&lt;li>&lt;a href="https://spring.io/blog/2015/01/08/spring-boot-1-2-1-released">Spring Boot 1.2.1&lt;/a> released&lt;/li>
&lt;li>The &lt;a href="http://beta.groovy-lang.org/download.html">Windows installer for Groovy 2.3.9&lt;/a> is available&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/553626428772995072">Grooscript Gradle plugin 0.9&lt;/a> released&lt;/li>
&lt;li>Groovy Android library &lt;a href="https://github.com/Arasthel/SwissKnife/blob/master/CHANGELOG.md">SwissKnife 1.2&lt;/a> released with @Parcelable transformation&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Jochen &amp;ldquo;Blackdrag&amp;rdquo; Theodorou behind the scenes of &lt;a href="http://blackdragsview.blogspot.fr/2015/01/indy-and-compilestatic-as-tag-team-to.html">bytecode optimizations for improving array access performance&lt;/a>&lt;/li>
&lt;li>Doug Borg describes what &lt;a href="http://dougborg.org/what-makes-a-good-build-dot-gradle">makes a good build.gradle&lt;/a>&lt;/li>
&lt;li>Reinout Korbee writes about &lt;a href="http://java.dzone.com/articles/run-your-antlr-dsl-groovy">running your Antlr DSL as a Groovy script&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://arzur.net/octopress/blog/2015/01/07/groovy-magic/">Some Groovy magic&lt;/a> by Erwan Arzur&lt;/li>
&lt;li>&lt;a href="http://blog.jdriven.com/2015/01/grails-preventing-naming-collisions/">Preventing naming collisions in Grails&lt;/a> by Albert van Veen&lt;/li>
&lt;li>Bertrand Goetzman shows how to display data coming from &lt;a href="http://www.odelia-technologies.com/content/afficher-des-donnees-provenant-dune-source-de-donnees-rest-dans-une-application-javafx-avec">REST with DataFX / JavaFX and Ratpack&lt;/a> (article in French)&lt;/li>
&lt;li>Robert McIntosh on &lt;a href="http://robertmcintosh.me/blog/2015/grails_with_billion_record_mongo_collection.html">Grails with a billion record Mongo collection&lt;/a>&lt;/li>
&lt;li>Peter Ledbrook on &lt;a href="http://blog.cacoethes.co.uk/groovyandgrails/groovy-in-light-of-java-8">Groovy in the light of Java 8&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.intelligrape.com/blog/the-biggest-grails-conference-in-india-grailsconf-2015/">Feedback from GrailsConf in India&lt;/a> by Amit Jain&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>Dan Woods will be speaking about &lt;a href="https://twitter.com/ratpackweb/status/552796932049883137">Ratpack online during an O&amp;rsquo;Reilly webinar&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/presentations/groovy-2-3">Groovy in 2014 and beyond&lt;/a> by Guillaume Laforge at SpringOne2GX 2014&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/presentations/groovy-grails-spring-boot">Making Spring Boot even groovier&lt;/a> by Graeme Rocher at SpringOne2GX 2014&lt;/li>
&lt;li>Slides form &amp;ldquo;&lt;a href="https://speakerdeck.com/kdabir/demystifying-gradle-dsl">Demystifying Gradle DSL&lt;/a>&amp;rdquo; presented at GrailsConf 2015, New Delhi&lt;/li>
&lt;/ul>
&lt;h2 id="interviews">Interviews&lt;/h2>
&lt;ul>
&lt;li>Guillaume Laforge interviewed at Devoxx 2014 about what makes &lt;a href="https://www.voxxed.com/blog/2015/01/groovy-swift-android-world/">Groovy the Swift of the Android world&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>MrHaki publishes the &lt;a href="http://mrhaki.blogspot.fr/2015/01/gradle-goodness-notebook-is-published.html">Gradle Goodness Notebook&lt;/a>&lt;/li>
&lt;li>Marco Vermeulen enjoys the &lt;a href="https://twitter.com/marc0der/status/554254484013330432">terrific section on writing nested Gradle plugin extension DSLs&lt;/a> in Tim Berglund&amp;rsquo;s &lt;a href="http://shop.oreilly.com/product/0636920019923.do">Gradle beyond the basics&lt;/a> book&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>Jacob Aae Mikkelsen &lt;a href="http://grydeske.net/news/show/78">Grails Diary&lt;/a>&lt;/li>
&lt;li>Testatoo, a web &lt;a href="https://github.com/Ovea/testatoo/blob/2.0.b3/src/doc/testatoo.adoc">user interface testing tool with a Groovy DSL&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-list">Mailing-list&lt;/h2>
&lt;ul>
&lt;li>A debate about &lt;a href="http://groovy.329449.n5.nabble.com/JVM-application-installer-via-maven-gradle-repo-td5722094.html">JVM application installers with Gradle&lt;/a> and more&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Iván Lopez participates in the Spring Boot book contest with a &lt;a href="http://lmivan.github.io/contest/#_technological_stack">full Groovy solution&lt;/a>! Star the project to make it win!&lt;/li>
&lt;li>Dan Woods shares some &lt;a href="https://twitter.com/danveloper/status/554008783077134336">examples of standalone Ratpack&lt;/a> 0.9.13&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Guillaume Laforge celebrated the &lt;a href="https://twitter.com/glaforge/status/552492079386198020">1000th star on Github for Groovy&lt;/a>, keep on spreading the word and double that number!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/glaforge/status/552492370768723968">Groovy Weekly released its 2000th news item&lt;/a> last week!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/ratpackweb/status/552796345749082112">Ratpack is teasing that the next release&lt;/a> is going to be a big &amp;ldquo;one&amp;rdquo;&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/552825902514847744">Groovy 2.4.0-rc-2&lt;/a> available through GVM&lt;/li>
&lt;li>Cédric Champeau asks the twittosphere what they&amp;rsquo;d like to &lt;a href="https://twitter.com/cedricchampeau/status/553107314182717440">see him speak about at GR8Conf&lt;/a>&lt;/li>
&lt;li>Andrew Reitz managed to get &lt;a href="https://twitter.com/andrewreitz_/status/553301907322576896">Spock tests running on Android&lt;/a>&lt;/li>
&lt;li>Vishal Savajiani claims &lt;a href="https://twitter.com/vishalgreat/status/553296725025120258">Groovy is a powerful language&lt;/a>! For the Internet of Things, for the Cloud, and more.&lt;/li>
&lt;li>The Groovy Android library &lt;a href="https://twitter.com/eugenekamenev/status/554716837720915969">SwissKnife is ready for Groovy 2.4&lt;/a>, reports Eugene Kamenev&lt;/li>
&lt;li>Mike Milinkovich is excited to see &lt;a href="https://twitter.com/mmilinkov/status/553556027913031680">Gradleware bringing first-class Gradle support to Eclipse&lt;/a>&lt;/li>
&lt;li>Iván López demonstrates you can be a &lt;a href="https://twitter.com/ilopmar/status/554219468843270144">full stack developer using only Groovy&lt;/a> with a concrete example&lt;/li>
&lt;li>&lt;a href="https://twitter.com/ankinson/status/554609893395927040">Spring Boot is ready for Groovy 2.4&lt;/a> announces Andy Wilkinson&lt;/li>
&lt;li>Dan Woods compares the &lt;a href="https://twitter.com/danveloper/status/554576240833986560">performance of Ratpack with Vert.x&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://devnexus.com/s/presentations?tags=groovy">Groovy at DevNexus&lt;/a>, with an epic keynote with the Groovy puzzlers and a Groovy workshop, by the JFrog team&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/greachconf/status/553125994114994176">Greach Call for Paper is closing in 10 days&lt;/a>, get ready!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gr8conf/status/552507512323600386">Last week for the GR8Conf Call for Paper&lt;/a>, hurry up!&lt;/li>
&lt;li>Cédric Champeau will speak about &lt;a href="https://twitter.com/poitoujug/status/552767245357944832">Groovy at Niort for the Poitou-Charentes JUG&lt;/a> on January 20th&lt;/li>
&lt;li>Greach announces its first accepted speakers: &lt;a href="https://twitter.com/greachconf/status/554937171027574784">Russel Winder&lt;/a>&lt;/li>
&lt;li>Only &lt;a href="https://twitter.com/greachconf/status/554952362042097665">2 days left for the early bird tickets for Greach&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/greachconf/status/554582324973539332">Dan Woods will be speaking at Greach&lt;/a> about Ratpack&lt;/li>
&lt;li>Another speaker announced for &lt;a href="https://twitter.com/greachconf/status/554597468977901569">Greach with MrHaki&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/GR8ConfUS/status/552672571754569728">GR8Conf US announces multiple tracks for the workshop day&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>The Groovy development team released the second release candidate of Groovy 2.4, please be sure to check your applications with this version and report any issue you may be finding, so that we can all ensure 2.4 is a rock-solid release.</p>
<p>Note the publication of MrHaki’s <a href="http://mrhaki.blogspot.fr/2015/01/gradle-goodness-notebook-is-published.html">Gradle Goodness Notebook</a>! If you’re using Gradle, this is worth the investment to have that resource handy for mastering your build automation.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Second-release-candidate-for-Groovy-2-4-td5722110.html">Groovy 2.4 second release candidate</a>, please help testing it!</li>
<li><a href="https://spring.io/blog/2015/01/08/spring-boot-1-2-1-released">Spring Boot 1.2.1</a> released</li>
<li>The <a href="http://beta.groovy-lang.org/download.html">Windows installer for Groovy 2.3.9</a> is available</li>
<li><a href="https://twitter.com/grooscript/status/553626428772995072">Grooscript Gradle plugin 0.9</a> released</li>
<li>Groovy Android library <a href="https://github.com/Arasthel/SwissKnife/blob/master/CHANGELOG.md">SwissKnife 1.2</a> released with @Parcelable transformation</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Jochen &ldquo;Blackdrag&rdquo; Theodorou behind the scenes of <a href="http://blackdragsview.blogspot.fr/2015/01/indy-and-compilestatic-as-tag-team-to.html">bytecode optimizations for improving array access performance</a></li>
<li>Doug Borg describes what <a href="http://dougborg.org/what-makes-a-good-build-dot-gradle">makes a good build.gradle</a></li>
<li>Reinout Korbee writes about <a href="http://java.dzone.com/articles/run-your-antlr-dsl-groovy">running your Antlr DSL as a Groovy script</a></li>
<li><a href="http://arzur.net/octopress/blog/2015/01/07/groovy-magic/">Some Groovy magic</a> by Erwan Arzur</li>
<li><a href="http://blog.jdriven.com/2015/01/grails-preventing-naming-collisions/">Preventing naming collisions in Grails</a> by Albert van Veen</li>
<li>Bertrand Goetzman shows how to display data coming from <a href="http://www.odelia-technologies.com/content/afficher-des-donnees-provenant-dune-source-de-donnees-rest-dans-une-application-javafx-avec">REST with DataFX / JavaFX and Ratpack</a> (article in French)</li>
<li>Robert McIntosh on <a href="http://robertmcintosh.me/blog/2015/grails_with_billion_record_mongo_collection.html">Grails with a billion record Mongo collection</a></li>
<li>Peter Ledbrook on <a href="http://blog.cacoethes.co.uk/groovyandgrails/groovy-in-light-of-java-8">Groovy in the light of Java 8</a></li>
<li><a href="http://www.intelligrape.com/blog/the-biggest-grails-conference-in-india-grailsconf-2015/">Feedback from GrailsConf in India</a> by Amit Jain</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Dan Woods will be speaking about <a href="https://twitter.com/ratpackweb/status/552796932049883137">Ratpack online during an O&rsquo;Reilly webinar</a></li>
<li><a href="http://www.infoq.com/presentations/groovy-2-3">Groovy in 2014 and beyond</a> by Guillaume Laforge at SpringOne2GX 2014</li>
<li><a href="http://www.infoq.com/presentations/groovy-grails-spring-boot">Making Spring Boot even groovier</a> by Graeme Rocher at SpringOne2GX 2014</li>
<li>Slides form &ldquo;<a href="https://speakerdeck.com/kdabir/demystifying-gradle-dsl">Demystifying Gradle DSL</a>&rdquo; presented at GrailsConf 2015, New Delhi</li>
</ul>
<h2 id="interviews">Interviews</h2>
<ul>
<li>Guillaume Laforge interviewed at Devoxx 2014 about what makes <a href="https://www.voxxed.com/blog/2015/01/groovy-swift-android-world/">Groovy the Swift of the Android world</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>MrHaki publishes the <a href="http://mrhaki.blogspot.fr/2015/01/gradle-goodness-notebook-is-published.html">Gradle Goodness Notebook</a></li>
<li>Marco Vermeulen enjoys the <a href="https://twitter.com/marc0der/status/554254484013330432">terrific section on writing nested Gradle plugin extension DSLs</a> in Tim Berglund&rsquo;s <a href="http://shop.oreilly.com/product/0636920019923.do">Gradle beyond the basics</a> book</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/78">Grails Diary</a></li>
<li>Testatoo, a web <a href="https://github.com/Ovea/testatoo/blob/2.0.b3/src/doc/testatoo.adoc">user interface testing tool with a Groovy DSL</a></li>
</ul>
<h2 id="mailing-list">Mailing-list</h2>
<ul>
<li>A debate about <a href="http://groovy.329449.n5.nabble.com/JVM-application-installer-via-maven-gradle-repo-td5722094.html">JVM application installers with Gradle</a> and more</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Iván Lopez participates in the Spring Boot book contest with a <a href="http://lmivan.github.io/contest/#_technological_stack">full Groovy solution</a>! Star the project to make it win!</li>
<li>Dan Woods shares some <a href="https://twitter.com/danveloper/status/554008783077134336">examples of standalone Ratpack</a> 0.9.13</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Guillaume Laforge celebrated the <a href="https://twitter.com/glaforge/status/552492079386198020">1000th star on Github for Groovy</a>, keep on spreading the word and double that number!</li>
<li><a href="https://twitter.com/glaforge/status/552492370768723968">Groovy Weekly released its 2000th news item</a> last week!</li>
<li><a href="https://twitter.com/ratpackweb/status/552796345749082112">Ratpack is teasing that the next release</a> is going to be a big &ldquo;one&rdquo;</li>
<li><a href="https://twitter.com/gvmtool/status/552825902514847744">Groovy 2.4.0-rc-2</a> available through GVM</li>
<li>Cédric Champeau asks the twittosphere what they&rsquo;d like to <a href="https://twitter.com/cedricchampeau/status/553107314182717440">see him speak about at GR8Conf</a></li>
<li>Andrew Reitz managed to get <a href="https://twitter.com/andrewreitz_/status/553301907322576896">Spock tests running on Android</a></li>
<li>Vishal Savajiani claims <a href="https://twitter.com/vishalgreat/status/553296725025120258">Groovy is a powerful language</a>! For the Internet of Things, for the Cloud, and more.</li>
<li>The Groovy Android library <a href="https://twitter.com/eugenekamenev/status/554716837720915969">SwissKnife is ready for Groovy 2.4</a>, reports Eugene Kamenev</li>
<li>Mike Milinkovich is excited to see <a href="https://twitter.com/mmilinkov/status/553556027913031680">Gradleware bringing first-class Gradle support to Eclipse</a></li>
<li>Iván López demonstrates you can be a <a href="https://twitter.com/ilopmar/status/554219468843270144">full stack developer using only Groovy</a> with a concrete example</li>
<li><a href="https://twitter.com/ankinson/status/554609893395927040">Spring Boot is ready for Groovy 2.4</a> announces Andy Wilkinson</li>
<li>Dan Woods compares the <a href="https://twitter.com/danveloper/status/554576240833986560">performance of Ratpack with Vert.x</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="http://devnexus.com/s/presentations?tags=groovy">Groovy at DevNexus</a>, with an epic keynote with the Groovy puzzlers and a Groovy workshop, by the JFrog team</li>
<li>The <a href="https://twitter.com/greachconf/status/553125994114994176">Greach Call for Paper is closing in 10 days</a>, get ready!</li>
<li><a href="https://twitter.com/gr8conf/status/552507512323600386">Last week for the GR8Conf Call for Paper</a>, hurry up!</li>
<li>Cédric Champeau will speak about <a href="https://twitter.com/poitoujug/status/552767245357944832">Groovy at Niort for the Poitou-Charentes JUG</a> on January 20th</li>
<li>Greach announces its first accepted speakers: <a href="https://twitter.com/greachconf/status/554937171027574784">Russel Winder</a></li>
<li>Only <a href="https://twitter.com/greachconf/status/554952362042097665">2 days left for the early bird tickets for Greach</a></li>
<li><a href="https://twitter.com/greachconf/status/554582324973539332">Dan Woods will be speaking at Greach</a> about Ratpack</li>
<li>Another speaker announced for <a href="https://twitter.com/greachconf/status/554597468977901569">Greach with MrHaki</a></li>
<li><a href="https://twitter.com/GR8ConfUS/status/552672571754569728">GR8Conf US announces multiple tracks for the workshop day</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #52</title><link>https://glaforge.dev/posts/2015/01/06/groovy-weekly-52/</link><pubDate>Tue, 06 Jan 2015 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2015/01/06/groovy-weekly-52/</guid><description>&lt;p>First of all, let me wish you a very Groovy year! May 2015 fulfill all its promises, keep you healthy, bring joy and hapiness around! And of course, may it be filled with tons of bits of Groovy code!&lt;/p>
&lt;p>Following up my plea for &lt;a href="https://github.com/groovy/groovy-core">more stars on Github&lt;/a>, we went from below 600 stars to beyond 1000! But we don’t need to stop there, so let’s keep starring and spreading the word!&lt;/p></description><content:encoded>
<![CDATA[<p>First of all, let me wish you a very Groovy year! May 2015 fulfill all its promises, keep you healthy, bring joy and hapiness around! And of course, may it be filled with tons of bits of Groovy code!</p>
<p>Following up my plea for <a href="https://github.com/groovy/groovy-core">more stars on Github</a>, we went from below 600 stars to beyond 1000! But we don’t need to stop there, so let’s keep starring and spreading the word!</p>
<p>Another thousand milestone reached with the 2000th news item delivered in Groovy Weekly this week!</p>
<p>Remember that you can <a href="http://bit.ly/groovy-weekly-subscribe">subscribe</a> to this Groovy Weekly series to get those news directly in your inbox, and that you can also <a href="http://bit.ly/groovyweekly">contribute news</a> yourself by filling a form if there’s something you want to tell the world!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/552051531399577600">Grooscript 1.0-rc-1</a>, be sure to test it before the final release!</li>
<li><a href="http://www.ratpack.io/versions/0.9.12">Ratpack 0.9.12</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>All in Together: <a href="http://www.bignerdranch.com/blog/all-in-together-android-studio-gradle-and-robolectric/">Android Studio, Gradle and Robolectric</a> by Jason Atwood</li>
<li><a href="http://groovymn.tumblr.com/post/106798263197/grails-angular-phantomjs-pdf-reports">Grails + Angular + PhantomJS = PDF Reports</a></li>
<li><a href="http://groovymn.tumblr.com/post/107130385197/grails-demo-pdf-app">Grails Demo PDF app</a></li>
<li>Second part on <a href="http://www.intelligrape.com/blog/grails-unique-constraint-optimization-part-2/">Grails unique constraint optimization</a> by Deepak Kumar Mittal</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="http://www.infoq.com/presentations/Groovy-ea-ops">Groovy for system administrators</a>, by Dan Woods, recorded at SpringOne2GX 2014</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>GPars 1.3 will feature a new Asciidoctor-based <a href="http://gpars.org/SNAPSHOT/aguide/html5/index.html">GPars user guide</a></li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/77">Grails Diary</a> week 51</li>
<li>This <a href="http://groovymn.tumblr.com/post/106705639062/this-month-in-gum">month in GUM</a></li>
</ul>
<h2 id="mailing-list">Mailing-list</h2>
<ul>
<li>Interesting dicussion on using <a href="http://groovy.329449.n5.nabble.com/Groovy-for-data-science-a-killer-application-for-Groovy-td5722061.html">Groovy for data science, as a killer application for Groovy</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau is musing with ideas of how to <a href="https://gist.github.com/melix/e27dce2c23467d47e7f4#file-cli-friendly-groovy-L10">improve Groovy command-line interactions</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>You can contribute to Grooscript by going through the <a href="https://twitter.com/grooscript/status/552052096183590912">Grooscript documentation</a></li>
<li>Andrew Reitz finds using <a href="https://twitter.com/andrewreitz_/status/552147730550644737">unit testing Android more bearable with Groovy</a></li>
<li>The Groovy team builds <a href="https://twitter.com/CedricChampeau/status/550982802272178178">Groovy on Continuous Integration with JDK 9 Jigsaw</a> too</li>
<li>Russell Hart explains how to <a href="https://twitter.com/rus_hart/status/551019292805070849">make authenticated user details available to Ratpack view model</a></li>
<li>Guillaume Laforge got a <a href="https://twitter.com/glaforge/status/552159421292630016">groovy electric guitar</a> for Christmas&hellip; or was it for his daughter?</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://plus.google.com/u/0/events/ceh2qfaalqv3vgnhq8tjoihjnic">10 languages in 10 minutes (including Groovy)</a> at the Coder Consortium in Sacramento on February 5th</li>
<li><a href="https://twitter.com/tbsalling/status/551979427924807680">GR8Conf Europe is looking for sponsors</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #51</title><link>https://glaforge.dev/posts/2014/12/30/groovy-weekly-51/</link><pubDate>Tue, 30 Dec 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/12/30/groovy-weekly-51/</guid><description>&lt;p>Here’s the end of a pretty Groovy year coming, giving room for an even groovier one!&lt;/p>
&lt;p>I hope you all enjoyed the holidays, and that you’re ready to pour in some more drops of Groovy into your software mixes!&lt;/p>
&lt;p>Don’t forget to continue &lt;a href="https://github.com/groovy/groovy-core">starring the Groovy project on Github&lt;/a>, if you haven’t done so, so we try to reach 1000 in the new year!&lt;/p>
&lt;p>What are going to be your new year’s resolutions? Perhaps some contributions to Groovy’s documentation, some bug fixes or new features?&lt;/p></description><content:encoded>
<![CDATA[<p>Here’s the end of a pretty Groovy year coming, giving room for an even groovier one!</p>
<p>I hope you all enjoyed the holidays, and that you’re ready to pour in some more drops of Groovy into your software mixes!</p>
<p>Don’t forget to continue <a href="https://github.com/groovy/groovy-core">starring the Groovy project on Github</a>, if you haven’t done so, so we try to reach 1000 in the new year!</p>
<p>What are going to be your new year’s resolutions? Perhaps some contributions to Groovy’s documentation, some bug fixes or new features?</p>
<p>In the meantime, happy reading, here are a few news bits for your consumption!</p>
<p>And let me wish you a very Groovy year!</p>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://arzur.net/octopress/blog/2014/12/28/metrics-plus-grails-equals-awesomeness/">Metrics + Grails = Awesomeness</a> by Erwan Arzur</li>
<li>Experiments (in French) with <a href="http://arzur.net/octopress/blog/2014/12/28/amusons-nous-avec-les-transactions-et-grails-dot-dot-dot/">Grails and transactions</a> by Erwan Arzur</li>
<li>A <a href="http://moduslaborandi.net/groovy-101-groovycalc-with-tests-and-travis-ci-integration/">Groovy 101</a> article also explaining how to use Groovy integration with Travis-CI</li>
<li><a href="http://davydotcom.com/blog/2014-12-21-spring-boot-with-the-asset-pipeline">Spring Boot with the Gradle Asset Pipeline</a> by David Estes</li>
<li>The <a href="http://www.intelligrape.com/blog/grails-stats/">Grails stats command gives useful insight</a> in the structure of your Grails projects</li>
<li>Abhinav Anand covers <a href="http://www.oodlestechnologies.com/blogs/tokenize%28%29-and-split%28%29-in-groovy">Groovy&rsquo;s tokenize() and split() methods</a></li>
<li><a href="http://www.oodlestechnologies.com/blogs/Nesting-custom-tags-in-grails">Nesting custom tags in Grails</a> by Abhinav Anand</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://www.bmeweb.it/category/groovy-weekly/">Groovy Weekly in Italian</a></li>
<li>GR8Conf US launched its <a href="http://gr8conf.us/#/">new website</a></li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/76">Grails Diary</a> week 52</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li><a href="https://github.com/uehaj/groovyz">Type classes in Groovy</a></li>
<li>Paul King shares an example of a simple <a href="http://groovyconsole.appspot.com/script/5646392177459200/">fluent English-like DSL using Groovy command chains</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau is trying to solve a long standing feature request about <a href="https://twitter.com/CedricChampeau/status/548477288627847168">enforcing &lsquo;final&rsquo;</a></li>
<li>Cédric Champeau coins a catchy movie title: <a href="https://twitter.com/cedricchampeau/status/549629946889777152">Groovy Wars ep1: The Phantom MetaClass</a></li>
<li>A <a href="https://twitter.com/agoncal/status/548805556992241664">tribute to Groovy</a> by the Mike Flowers Pops celebrated by Antonio Goncalves</li>
<li>Craig Burke is looking forward to release his <a href="https://twitter.com/craigburke1/status/548546842892713984">document builder</a> by the end of this year</li>
<li>Dierk König highlights <a href="https://twitter.com/mittie/status/548508666358669312">Groovy&rsquo;s ability to take on the challenge to support many programming language concepts</a></li>
<li>Dean Del Ponte notices that <a href="https://twitter.com/ddelponte/status/547841332224339969">Grails 3 apps can be run like any Gradle app within IntelliJ IDEA Community edition</a></li>
<li><a href="https://twitter.com/gvmtool/status/549330992197304320">AsciidoctorJ is now available on GVM</a></li>
<li>Dierk König is <a href="https://twitter.com/mittie/status/549587163151872000">leveraging Groovy categories for an elegant integration with Frege</a>&rsquo;s data structures</li>
<li><a href="https://twitter.com/marc0der/status/549619315683889153">What makes the Groovy community shine</a>, according to Marco Vermeulen? Its people are kind and helpful.</li>
<li>Marco Vermeulen wishes the <a href="https://twitter.com/marc0der/status/549613033031729152">Gradle task API had dependency injection to ease testing</a></li>
<li>Marco Vermeulen is finding <a href="https://twitter.com/marc0der/status/549539981069656064">Groovy productive for authoring Gradle plugins</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Venkat Subramaniam to demonstrate how to <a href="https://twitter.com/GrailsConf/status/548396817033879552">apply Groovy closures for fun and productivity</a> at GraisConf India</li>
<li>Cédric Champeau is going to <a href="https://twitter.com/cedricchampeau/status/549478535216496640">speak about why using Groovy at the Poitou JUG</a></li>
<li>Cédric Champeau will be speaking about <a href="https://twitter.com/CedricChampeau/status/549871190245126144">Groovy on Android at the MCE 2015</a> conference for mobile developers</li>
<li>The <a href="https://twitter.com/greachconf/status/549507636719128579">Greach conference launched its Lanyrd</a> page</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>First release candidate of Groovy 2.4</title><link>https://glaforge.dev/posts/2014/12/24/first-release-candidate-of-groovy-2-4/</link><pubDate>Wed, 24 Dec 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/12/24/first-release-candidate-of-groovy-2-4/</guid><description>&lt;p>Hot on the heels of our 2.3.9 update, the Groovy team is happy to release the first release candidate of Groovy 2.4 as another Xmas present!&lt;/p>
&lt;p>This release candidate is our upcoming new major version of Groovy, including:&lt;/p>
&lt;ul>
&lt;li>official support for the Android development platform: you can now develop full Android applications in Groovy, dramatically reducing boilerplate code while keeping performance and memory consumption at the same level as Java apps&lt;/li>
&lt;li>performance optimizations: lots of improvements have been implemented in both statically compiled Groovy code and dynamic code. (example report of an illustrative &lt;a href="https://gist.github.com/melix/ea819c77c4b568660877">micro-benchmark&lt;/a>)&lt;/li>
&lt;li>optimized memory use: reworked some compiler internals to reduce memory consumption&lt;/li>
&lt;li>traits refinements: like the ability to tell that a trait can only be applied to a specific type hierarchy&lt;/li>
&lt;li>and as usual, lots of bugfixes&lt;/li>
&lt;/ul>
&lt;p>We’re planning on releasing the final Groovy 2.4 version in January, once we’re happy with the stability and feedback from the community.&lt;/p></description><content:encoded>
<![CDATA[<p>Hot on the heels of our 2.3.9 update, the Groovy team is happy to release the first release candidate of Groovy 2.4 as another Xmas present!</p>
<p>This release candidate is our upcoming new major version of Groovy, including:</p>
<ul>
<li>official support for the Android development platform: you can now develop full Android applications in Groovy, dramatically reducing boilerplate code while keeping performance and memory consumption at the same level as Java apps</li>
<li>performance optimizations: lots of improvements have been implemented in both statically compiled Groovy code and dynamic code. (example report of an illustrative <a href="https://gist.github.com/melix/ea819c77c4b568660877">micro-benchmark</a>)</li>
<li>optimized memory use: reworked some compiler internals to reduce memory consumption</li>
<li>traits refinements: like the ability to tell that a trait can only be applied to a specific type hierarchy</li>
<li>and as usual, lots of bugfixes</li>
</ul>
<p>We’re planning on releasing the final Groovy 2.4 version in January, once we’re happy with the stability and feedback from the community.</p>
<p>You can read the <a href="https://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20785">Groovy 2.4-rc-1 release notes</a> to learn more about the tickets closed, and head over to the <a href="http://beta.groovy-lang.org/download.html">download</a> section of the beta Groovy website to get the latest and latest bits on your computer! The documentation for this version can be found <a href="http://docs.groovy-lang.org/2.4.0-rc-1/html/documentation/">here</a>.</p>
<p>We need your help to test drive this release candidate! We would greatly appreciate if you check this version against your projects and report back any regression or blocker that you come across.</p>
<p>Thanks a lot for all your contributions and support!</p>
<p>Keep on groovy-ing, and groovy holidays!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #50</title><link>https://glaforge.dev/posts/2014/12/24/groovy-weekly-50/</link><pubDate>Wed, 24 Dec 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/12/24/groovy-weekly-50/</guid><description>&lt;p>Lots among us are celebrating Christmas this week, and Groovy Weekly is happy to wish you all a Merry Christmas! And there’s also an anniversary, as it’s been one year that Groovy Weekly was launched, with the first edition on December 24th (hence my excuse to publish this column on Wednesday 24th instead of on Tuesday as usual)!&lt;/p>
&lt;p>For Christmas, the Groovy team decided to release a &lt;a href="https://glaforge.dev/posts/2014/12/24/first-release-candidate-of-groovy-2-4/">first release candidate for Groovy 2.4&lt;/a>, as well as a Groovy 2.3.9 update.&lt;/p></description><content:encoded>
<![CDATA[<p>Lots among us are celebrating Christmas this week, and Groovy Weekly is happy to wish you all a Merry Christmas! And there’s also an anniversary, as it’s been one year that Groovy Weekly was launched, with the first edition on December 24th (hence my excuse to publish this column on Wednesday 24th instead of on Tuesday as usual)!</p>
<p>For Christmas, the Groovy team decided to release a <a href="https://glaforge.dev/posts/2014/12/24/first-release-candidate-of-groovy-2-4/">first release candidate for Groovy 2.4</a>, as well as a Groovy 2.3.9 update.</p>
<p>Did you see this little guerrilla marketing campaign by Guillaume Laforge to encourage you all to make Groovy trendy by <a href="https://twitter.com/glaforge/status/545504762456969216">starring Groovy on Github</a>?</p>
<p>Thanks for helping us spread the word!</p>
<p>And last news item of the week, <a href="http://www.bmeweb.it/settimanale-groovy-2014-49/">Groovy Weekly is being translated in Italian</a>!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://glaforge.dev/posts/2014/12/24/first-release-candidate-of-groovy-2-4/">Groovy 2.4-rc-1</a> released!</li>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-2-3-9-td5721985.html">Groovy 2.3.9</a> released</li>
<li><a href="https://github.com/javaConductor/gserv/wiki/gServ-Home">gServ</a>, a new micro-service inspired, container-less tool to deploy Groovy REST services</li>
<li><a href="https://twitter.com/pledbrook/status/545178973651894272">Lazybones Gradle plugin v1.2.3</a></li>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-VFS-1-0-Beta-2-td5721979.html">Groovy VFS 1.0 Beta 2</a></li>
<li><a href="https://twitter.com/tim_yates/status/547510301634338816">Groovy-Stream v0.9</a> with new repeat methods</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="https://www.voxxed.com/blog/2014/12/grails-domain-classes-special-presentation-requirements/">Grails domain classes and special presentation requirements</a> by Ted Vinke</li>
<li>A <a href="http://mnmlst-dvlpr.blogspot.de/2014/12/my-lightweight-release-process.html">lightweight release process to Maven Central with Gradle</a>, TravisCI, Bintray</li>
<li>An <a href="http://blog.soat.fr/2014/12/spock-tester-autrement/">introduction to Spock</a> in French</li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/12/gradle-goodness-rename-ant-task-names.html">rename Ant task names when importing Ant build files</a></li>
<li>Iván López&rsquo; <a href="http://www.kaleidos.net/blog/759/ggx-my-talk-and-general-impressions/">impressions of the Groovy Grails eXchange conference</a></li>
<li><a href="http://www.intelligrape.com/blog/groovy-annotations-for-logging/">Groovy annotations for logging</a> by Neetesh Bhardwaj</li>
<li><a href="http://www.intelligrape.com/blog/grails-performance-optimization-unique-constraint/">Grails performance optimization with the unique constraint</a> by Deepak Kumar Mittal</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="https://parleys.com/play/5471dd16e4b0e15e672384e7/chapter0/about">Groovy in the light of Java 8</a> by Guillaume Laforge at Devoxx 2014 (Parleys subscription required)</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>A <a href="http://www.oreilly.com/pub/e/3286">Gradle for Android webcast</a> by Ken Kousen on O&rsquo;Reilly</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Julien Viet announces a <a href="https://groups.google.com/forum/#!msg/vertx/Pr8QejfMIj0/YGQ_QRQyK2cJ">preview of Vert.x 3</a> (Groovy language support included)</li>
<li>The <a href="http://www.bmeweb.it/settimanale-groovy-2014-49/">Groovy Weekly now in Italian</a>, thanks to Giuliano Lo Iacono!</li>
<li><a href="http://grydeske.net/news/show/75">Grails Diary week 51</a> by Jacob Aae Mikkelsen</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>The new repeat method in Groovy-stream helps generate the <a href="https://twitter.com/tim_yates/status/547517168708222976">fizz-buzz sequence without the need of conditionals</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/glaforge/status/545504762456969216">Star the Groovy project on Github</a> asks Guillaume Laforge!</li>
<li>Jeff Brown calls for <a href="https://twitter.com/jeffscottbrown/status/547516371412975617">help to test drive the Groovy 2.4 release candidate</a>!</li>
<li>Jörg Prante finds Groovy so much fun that he <a href="https://twitter.com/xbib/status/546425480657010690">embeds Ratpack in an ElasticSearch plugin</a></li>
<li>New documentation on <a href="https://twitter.com/ratpackweb/status/541908183212490752">how to use Jackson in Ratpack</a></li>
<li>The Grain static site generator releases a <a href="https://twitter.com/grainframework/status/545167185669545984">company website theme with a blog</a> included</li>
<li>Chris Earle reminds us that <a href="https://twitter.com/pickypg/status/545664315026845698">ElasticSearch&rsquo;s Groovy client</a> has been updated with full compatibility for ElasticSearch 1.4.2</li>
<li>Eugene Kamenev enjoys the <a href="https://twitter.com/eugenekamenev/status/545657795404525568">greater Groovy support in Android Studio</a></li>
<li>Greg Williams believes <a href="https://twitter.com/greg2020/status/545611812087943168">Groovy will become the Swift for Android</a></li>
<li>Peter Ledbrook added <a href="https://twitter.com/pledbrook/status/545881211047268352">documentation on the config command of Lazybones</a></li>
<li><a href="https://twitter.com/gvmtool/status/545978461509394432">Lazybones 0.8</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/545960385237778432">Groovy 2.3.9</a> available on GVM</li>
<li>Iván López is having <a href="https://twitter.com/ilopmar/status/545238870485639169">fun with Groovy on Android</a></li>
<li><a href="https://twitter.com/grooscript/status/546274756484673536">GrooScript added Continous Integration</a> thanks to SnapCI</li>
<li>Cédric Champeau found a comic strip with a <a href="https://twitter.com/CedricChampeau/status/547513924157853697">hand G sign for top geekery</a>&hellip; and with a Groovy t-shirt!</li>
<li>The <a href="https://twitter.com/CedricChampeau/status/547503135011643394">JFrog Bintray team&rsquo;s been helping the Groovy team with deployment and release issues</a>, thanks guys!</li>
<li><a href="https://twitter.com/gvmtool/status/547502562791161860">Groovy 2.4-rc-1</a> available on GVM</li>
<li><a href="https://twitter.com/chriswhocodes/status/547403796754993153?cn=ZmF2b3JpdGVfbWVudGlvbmVkX3VzZXI%3D&amp;refsrc=email">JITWatch now has Groovy support</a> announced Chris Newland</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/greachconf/status/546968408009371649">Greach call for papers has been extended to January 18th</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #49</title><link>https://glaforge.dev/posts/2014/12/16/groovy-weekly-49/</link><pubDate>Tue, 16 Dec 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/12/16/groovy-weekly-49/</guid><description>&lt;p>This has been a super busy week, in particular with the 7th edition of the Groovy Grails eXchange conference in London. You’ll find lots of slides and videos already published online, so you won’t miss a beat! For instance don’t miss this talk from Shuichisan how &lt;a href="https://www.skillsmatter.com/skillscasts/6107-developing-api-platform-in-groovy-at-ratuken">Groovy is used by Japan’s Rakuten mobile backend as a service platform at scale&lt;/a>!&lt;/p>
&lt;p>A special highlight in the articles section to Cédric’s article on &lt;a href="http://melix.github.io/blog/2014/12/10-things-static-cant-do.html">10 things your static language can’t do&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>This has been a super busy week, in particular with the 7th edition of the Groovy Grails eXchange conference in London. You’ll find lots of slides and videos already published online, so you won’t miss a beat! For instance don’t miss this talk from Shuichisan how <a href="https://www.skillsmatter.com/skillscasts/6107-developing-api-platform-in-groovy-at-ratuken">Groovy is used by Japan’s Rakuten mobile backend as a service platform at scale</a>!</p>
<p>A special highlight in the articles section to Cédric’s article on <a href="http://melix.github.io/blog/2014/12/10-things-static-cant-do.html">10 things your static language can’t do</a>.</p>
<h2 id="releases">Releases</h2>
<ul>
<li>A new version of the <a href="https://twitter.com/marioggar/status/544814985776734208">Grooid template for Groovy on Android development</a></li>
<li><a href="https://spring.io/blog/2014/12/11/spring-boot-1-2-0-released">Spring Boot 1.2</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Cédric Champeau lists <a href="http://melix.github.io/blog/2014/12/10-things-static-cant-do.html">10 things your static language can&rsquo;t do</a></li>
<li><a href="http://matthurne.com/blog/2014/using-groovy-extension-modules-with-gradle-shadow.html">Using Groovy extension modules with the Gradle Shadow plugin</a> by Matt Hurne</li>
<li><a href="http://www.objectpartners.com/2014/12/11/copying-multiple-directories-in-a-single-gradle-task-with-up-to-date-checking/">Copying multiple directories in a single Gradle task with up-to-date checking</a> by Craig Atkinson</li>
</ul>
<h2 id="groovy-grails-exchange-interviews">Groovy Grails eXchange Interviews</h2>
<ul>
<li><a href="http://blog.skillsmatter.com/2014/12/15/while-its-compiling-skills-matter-interviews-graeme-rocher/">Graeme Rocher interviewed about Grails</a></li>
<li><a href="http://blog.skillsmatter.com/2014/12/12/while-its-compiling-skills-matter-interviews-guillaume-laforge/">Guillaume Laforge interviewed about Groovy</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Paul King discusses the <a href="http://fr.slideshare.net/paulk_asert/awesome-groovy">good parts of Groovy</a> at YOW! 2014</li>
<li>All the <a href="https://www.skillsmatter.com/conferences/1957-groovy-grails-exchange-2014#skillscasts">presentations of the Groovy Grails eXchange 2014</a> conference are being put online on the conference website (you need to be logged-in to view the videos)</li>
<li>Day one
<ul>
<li>Guillaume Laforge&rsquo;s <a href="https://skillsmatter.com/skillscasts/6047-what-s-up-in-the-groovy-world">Groovy update keynote</a></li>
<li>Vladimír Oraný presented <a href="https://www.skillsmatter.com/skillscasts/6075-feed-your-grails-karma">&ldquo;Feed you Grails karma&rdquo;</a></li>
<li>Peter Ledbrook presented <a href="https://www.skillsmatter.com/skillscasts/5485-groovy-for-java-developers">“Groovy for Java developers”</a></li>
<li>Shuichi Suzuki spoke about &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6107-developing-api-platform-in-groovy-at-ratuken">Developing API Platform in Groovy at Ratuken</a>&rdquo;</li>
<li>Alvaro Sanchez-Mariscal on &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6058-stateless-authentication-for-microservices">Stateless authentication for microservices</a>&rdquo;</li>
<li>Russel Winder on &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6081-spocktacular-testing">Spocktacular testing</a>&rdquo;</li>
<li>Jeff Brown on &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6062-groovy-ast-transformations-and-type-checking-extensions">Groovy AST Transformations And Type Checking Extensions</a>&rdquo;</li>
<li>Schalk Cronjé on “<a href="https://www.skillsmatter.com/skillscasts/6049-groovy-vfs">Groovy VFS</a>”</li>
<li>Guillaume Laforge on &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6072-behind-the-scenes-the-new-groovy-website">Behind the scenes of the new Groovy website and documentation</a>&rdquo;</li>
<li>Markus Schlichting on &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6084-gradle-harder-better-stronger-faster">Gradle: harder, better, stronger, faster</a>&rdquo;</li>
</ul>
</li>
<li>Day two
<ul>
<li>Graeme Rocher&rsquo;s <a href="https://www.skillsmatter.com/skillscasts/4958-keynote-grails-three-point-zero-preview">Grails 3</a> keynote</li>
<li>Guillaume Laforge on “<a href="https://skillsmatter.com/skillscasts/6073-groovy-and-android-a-winning-pair">Groovy on Android</a>” presentation</li>
<li>Iván López on “<a href="https://skillsmatter.com/skillscasts/6074-grails-and-the-real-time-world#video">Grails and the real-time world</a>”</li>
<li>Marcin Erdmann on “<a href="https://skillsmatter.com/skillscasts/6064-running-an-open-source-project">Running an Open Source project</a>”</li>
<li>Jeff Brown on “<a href="https://www.skillsmatter.com/skillscasts/6063-restful-grails-2">Restful Grails 2</a>”</li>
<li>Marco Vermeulen on &ldquo;<a href="https://www.skillsmatter.com/skillscasts/6050-micro-service-architecture-with-spring-boot-and-groovy">Micro Service Architecture with Spring Boot and Groovy</a>&rdquo;</li>
<li>David Dawson on “<a href="https://www.skillsmatter.com/skillscasts/6044-forces-on-groovy-architecture">Forces on Groovy architecture</a>”</li>
<li>Guillaume Laforge on “<a href="https://www.skillsmatter.com/skillscasts/4957-groovy-with-style">Groovy with Style</a>”</li>
</ul>
</li>
<li>Slides from the Groovy Grails eXchange 2014 conference:
<ul>
<li>Guillaume Laforge published his slides from the “<a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-groovy-grails-exchange-2014">Groovy update</a>” talk</li>
<li>Guillaume Laforge published the slides of the &ldquo;<a href="https://speakerdeck.com/glaforge/behind-the-scenes-of-the-new-groovy-website-and-documentation">Behind the Groovy website and documentation</a>&rdquo; presentation</li>
<li>Guillaume Laforge&rsquo;s “<a href="https://speakerdeck.com/glaforge/groovy-on-android-groovy-grails-exchange-2014">Groovy on Android</a>” slides</li>
<li>Guillaume Laforge’s “<a href="https://speakerdeck.com/glaforge/groovy-with-style-groovy-grails-exchange-2014">Groovy with style</a>” presentation</li>
<li>Russel Winder published the slides of his <a href="https://twitter.com/russel_winder/status/543038550631915520">&ldquo;Spocktacular testing&rdquo;</a> talk</li>
<li>Vladimír Oraný published the slides of his &ldquo;<a href="http://fr.slideshare.net/vladimirorany/feed-your-grails-karma-ggx-2014">Feed your Grails karma</a>&rdquo; talk</li>
<li>Slides of &ldquo;<a href="http://marcovermeulen.github.io/spring-boot-groovy-talk/#/">Micro-service architecture with Spring Boot and Groovy</a>&rdquo; by Marco Vermeulen</li>
</ul>
</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="http://gr8conf.eu/#/">new GR8Conf Europe website</a> is launched!</li>
<li>You can <a href="https://youtrack.jetbrains.com/issue/IDEA-130198">vote for solving this regression on the support of IntelliJ IDEA for standard Groovy extension methods</a></li>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/74">Grails Diary</a> week 50</li>
<li>This <a href="http://groovymn.tumblr.com/post/104760987832/this-month-in-gum">month in the Groovy Users of Minnesota</a></li>
</ul>
<h2 id="mailing-lists">Mailing-lists</h2>
<ul>
<li>An ongoing mailing-list thread is wondering <a href="http://groovy.329449.n5.nabble.com/The-single-USP-for-Groovy-td5721782.html">what is the unique selling point of Groovy</a>. What&rsquo;s yours?</li>
<li>Another ongoing thread on the Groovy lists is a discussion on <a href="http://groovy.329449.n5.nabble.com/Groovy-Value-Proposition-Compared-with-Java-8-td5721855.html">what is Groovy&rsquo;s value proposition compared with Java 8</a>. What&rsquo;s your take on that?</li>
<li>Alain Stalder shares a <a href="http://groovy.329449.n5.nabble.com/quot-Grey-quot-Sets-of-Groovy-Scripts-A-Case-Story-with-Groovy-and-Grengine-td5721936.html">case story with Groovy and his Grengine Groovy-powered scripting solution</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>An example of a <a href="http://sharing.beakernotebook.com/gist/anonymous/3fcbb2e8c0d67079d313">Beaker data scientist notebook using Groovy</a></li>
<li>Vladimír Oraný pushed online his <a href="https://github.com/musketyr/earls-list">demo application of his &ldquo;Feed Your Grails Karma&rdquo;</a> Groovy Grails eXchange talk</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau thinks <a href="https://twitter.com/cedricchampeau/status/542996207854190593">Groovy&rsquo;s uniqueness is its runtime and compile-time capabilities</a></li>
<li>Craig Burke is making progress on his <a href="https://twitter.com/craigburke1/status/543064329533521921">Groovy document builder DSL</a></li>
<li>Luis Arias congratulates <a href="https://twitter.com/realkaaloo/status/543332004050530305">Guillaume Laforge for his 11+ years on the Groovy</a> project! How time flies…</li>
<li>Mario García published a <a href="https://twitter.com/marioggar/status/543358372477960192">Lazybones template for getting started with Groovy on Android development</a></li>
<li>During Graeme Rocher&rsquo;s keynote at Groovy Grails eXchange, Marcin Erdmann notes that <a href="https://twitter.com/marcinerdmann/status/543343346081890304">Grails 3 will use Geb for functional tests by default</a></li>
<li>Schalk Cronjé draws the <a href="https://twitter.com/ysb33r/status/542988039606521856">main points of Guillaume Laforge&rsquo;s Groovy update</a> presentation</li>
<li><a href="https://twitter.com/gvmtool/status/542985840545529856">Spring Boot 1.1.10</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/542971505278459904">Spring Boot 1.2</a> available on GVM</li>
<li>Russel Winder notes <a href="https://twitter.com/russel_winder/status/542984966058311680">Groovy Macros will be re-scheduled to Groovy 2.5</a> to gather further feedback and real use-cases</li>
<li>Danny Hyun did a quick photoshop montage of <a href="https://twitter.com/lspacewalker/status/542340712134868992">Guillaume Laforge as the Fresh Prince of Bel-Air</a>, as Guillaume&rsquo;s transliteration name in English would be Will Smith!</li>
<li>Tomas Lin also reinterprets the <a href="https://twitter.com/tomaslin/status/542348040552448000">Fresh Prince of Bel Air family as the Groovy family</a></li>
<li>Sean Gilligan shows off a <a href="https://twitter.com/msgilligan/status/542606254405713920">screenshot of Groovy running on iOS</a></li>
<li>A nice list of useful and <a href="https://twitter.com/macg33zr/status/543046348153847808">interesting Gradle plugins to investigate</a> and play with</li>
<li>Brian Johnsen believes the <a href="https://twitter.com/brianjohnsendk/status/543061487741595649">Groovy core team have taken executable documentation to a new level</a></li>
<li>Angel Ruiz believes <a href="https://twitter.com/aruizca/status/543188269605732352">Groovy&rsquo;s executable documentation is how tech documentation should be done</a> these days</li>
<li>In <a href="https://twitter.com/ratpackweb/status/543187563947651072">Ratpack 0.9.12, a type can implement Renderable</a> to make it renderable</li>
<li>Robert Fletcher notes that <a href="https://twitter.com/rfletcherEW/status/543108794150944768">Groovy method closures can be coerced to Guava functions</a>, or actually any functional interface where Java 8 lambdas would be expected</li>
<li>Russel Winder remarks that <a href="https://twitter.com/russel_winder/status/543347767096410113">Grails 3 will be able to both be a serious full stack web application framework and a lightweight microframework</a></li>
<li>Phil Webb counted 194 people who <a href="https://twitter.com/phillip_webb/status/543271226542522368">voted for a Gradle &ldquo;provided&rdquo; scope</a></li>
<li>&ldquo;<a href="https://twitter.com/russel_winder/status/543356824947556353">The Java used on Android is out of the stone age</a>&rdquo;, said Guillaume Laforge during his Groovy on Android talk, and Russel Winder thinks it&rsquo;s worse than that!</li>
<li>Russel Winder believes <a href="https://twitter.com/russel_winder/status/543357338816888832">Android needs Java 8, or developers can just use Groovy, and that&rsquo;s what the New York Times decided to do</a> for its Android application</li>
<li>Another great feature of <a href="https://twitter.com/mgdelacroix/status/543344032475533312">Grails 3 is that it&rsquo;s coming integrated with tools that projects usually need: Gradle, Spock, Geb</a></li>
<li>Mario García is working on a new <a href="https://twitter.com/marioggar/status/543394656726556672">Lazybone template for authoring AST transformations for Groovy on Android</a></li>
<li>A <a href="https://twitter.com/lhotari/status/543488286229286912">picture of the Grails team and the Groovy lead</a> at Groovy Grails eXchange 2014</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/naresha_k/status/542982488658677760">Venkat Subramaniam will be speaking at GrailsConf India</a> on pleasure and perils of dynamic ecosystem</li>
<li>Read about the <a href="http://grailsconf.in/sessions">sessions of GrailsConf India</a></li>
<li>The <a href="http://www.meetup.com/Gr8Ladies/">GR8Ladies have created a Meetup page</a> to get the latest news, upcoming event announcements, etc</li>
<li>You can watch the <a href="https://skillsmatter.com/conferences/1957-groovy-grails-exchange-2014#photos">pictures of Groovy Grails eXchange</a> online</li>
<li><a href="https://twitter.com/gr8confus/status/544522716897083392">GR8Conf US 2015</a> will take place July 29th-31st</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #48</title><link>https://glaforge.dev/posts/2014/12/09/groovy-weekly-48/</link><pubDate>Tue, 09 Dec 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/12/09/groovy-weekly-48/</guid><description>&lt;p>Almost a year since I started the Groovy Weekly newsletter. And perhaps for the first anniversary, in a handful of weeks, we’ll have reached 2000 news items shared with you all in this column!&lt;/p>
&lt;p>This week is a London-ian one, as lots of the Groovy ecosystem inhabitants are migrating to the UK to celebrate the Groovy Grails eXchange conference. I’m looking forward to seeing some of you this week there. It also means that next week, you’ll likely see many links to slides and videos of the conference.&lt;/p></description><content:encoded>
<![CDATA[<p>Almost a year since I started the Groovy Weekly newsletter. And perhaps for the first anniversary, in a handful of weeks, we’ll have reached 2000 news items shared with you all in this column!</p>
<p>This week is a London-ian one, as lots of the Groovy ecosystem inhabitants are migrating to the UK to celebrate the Groovy Grails eXchange conference. I’m looking forward to seeing some of you this week there. It also means that next week, you’ll likely see many links to slides and videos of the conference.</p>
<p>And congrats to the Griffon team for a nice Griffon 2.1 release!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Griffon-2-1-0-released-td5721850.html">Griffon 2.1.0</a> released</li>
<li><a href="https://twitter.com/grooscript/status/540115721611927552">GrooScript 0.6.3</a> released, equivalent of the first release candidate for 1.0</li>
<li>Spring Tool Suite and <a href="https://spring.io/blog/2014/12/02/spring-tool-suite-and-groovy-grails-tool-suite-3-6-3-released">Groovy/Grails Tool Suite 3.6.3</a> released</li>
<li>New versions of Craig Burke&rsquo;s <a href="https://twitter.com/craigburke1/status/539876227382005760">Grails and Angular.JS Gradle asset pipeline plugins</a></li>
<li>Groovy-based <a href="http://jenkins-ci.org/content/workflow-plugin-10">Jenkins Workflow plugin reaches 1.0</a></li>
<li><a href="https://twitter.com/grooscript/status/540597302704865280">GrooScript Gradle plugin v0.8</a> is out</li>
<li><a href="https://twitter.com/gradleplugins/status/541981425181155329">Asciidoctor Gradle plugin v1.5.2</a> released</li>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-VFS-1-0-Beta-1-td5721839.html">Groovy VFS 1.0 beta 1</a> released</li>
<li>The <a href="http://groovy.329449.n5.nabble.com/Windows-Installer-for-Groovy-2-3-8-td5721841.html">Groovy Windows installer for Groovy 2.3.8</a> is available</li>
<li><a href="https://twitter.com/andreyhihlovski/status/542045672623861760">Gretty 1.1.8</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>The <a href="http://beta.groovy-lang.org/style-guide.html">Groovy style guide</a> document migrates to the new documentation and website</li>
<li>Kyle Boon mixes <a href="http://kyleboon.org/blog/2014/08/14/ratpack-plus-docker-plus-gradle/">Ratpack, Docker and Gradle</a></li>
<li>Four steps to a <a href="http://blog.codenvy.com/cooking-custom-build-environments/">Docker-based build environment with CodeEnvye, showcasing Grails</a></li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/12/gradle-goodness-skip-building-project.html">skip building project dependencies</a></li>
<li>Jochen Theodorou gives a deeper look at <a href="http://blackdragsview.blogspot.de/2014/12/a-deeper-look-at-default-methods.html">interface default methods conflicts and proxies</a></li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/12/gradle-goodness-continue-build-even.html">continue build even with failed tasks</a></li>
<li><a href="http://hussain.io/2014/12/setting-up-sonarqube-for-grails-project/">Setting up SonarQube for Grails projects</a> by Hussain Fakhruddin</li>
<li>Parampreet Singh on <a href="http://www.intelligrape.com/blog/groovy-goodness-readwritelocks/">Groovy&rsquo;s @WithReadLock and @WithWriteLock transformations</a></li>
<li><a href="http://www.intelligrape.com/blog/restricting-concurrent-sessions-for-a-single-user-using-grails-and-spring-security/">Restricting concurrent sessions for a single user using Grails and Spring Security</a></li>
<li>More about the <a href="http://davydotcom.com/blog/2014-12-03-asset-pipeline-2-0">Asset-Pipeline Gradle plugin v2.0</a> by David Estes</li>
<li><a href="http://www.objectpartners.com/2014/12/03/case-insensitive-criteria-ordering-on-child-properties/">Case-insensitive criteria ordering on child properties</a> by Igor Shults</li>
<li><a href="http://www.groovy-code.com/2014/12/effective-grails-plugin-development.html">Effective Grails plugin development with in-place plugins</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Guillaume Laforge presented about <a href="https://twitter.com/virtualjug/status/539848790732472320">What makes Groovy groovy at the Virtual JUG</a></li>
<li>SpringOne2GX 2014 presentations on InfoQ
<ul>
<li><a href="http://www.infoq.com/presentations/android-groovy-jvm">Groovy on Android, a winning pair</a>, by Cédric Champeau</li>
<li><a href="http://www.infoq.com/presentations/grails-microservices-arch">Experiences using Grails in a microservice architecture</a>, by Jeff Beck</li>
<li><a href="http://www.infoq.com/presentations/groovy-java8-streams-api">How to get Groovy with Java 8</a>, by Peter Ledbrook</li>
</ul>
</li>
<li>Craig Atkinson delivered a GR8Ladies <a href="https://twitter.com/craigatk1/status/542041878276096000">workshop about TDD with Groovy and Spock</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://android-developers.blogspot.fr/2014/12/android-studio-10.html">Android Studio 1.0</a> out with its Gradle built-in support for building Android applications</li>
<li>Hans Dockter delivers the <a href="http://www.gradleware.com/newsletter/gradleware-newsletter-november-2014/">November Gradleware newsletter</a></li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/73">Grails Diary</a> week 49</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Bertrand Goetzmann released a screencast showing how to <a href="https://www.youtube.com/watch?v=yktftRpoeg4&amp;feature=youtu.be&amp;a">build a beautiful timeline with Linked Data, Spring Boot, Groovy, GroovySparql, and TimelineJS</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Following up a <a href="https://twitter.com/cedricchampeau/status/539802492000026624">conversation</a> on Twitter with Peter Ledbrook, Guillaume Laforge shares a gist showing <a href="https://gist.github.com/glaforge/1f481e0f0d043260aa9a">Groovy&rsquo;s List and Map coercion to types on assignments</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>The GrooScript project encourages you to <a href="https://twitter.com/grooscript/status/540169463271018496">try out GrooScript, to make a solid 1.0 release</a></li>
<li>Cédric Champeau shares the results of the Fibonacci <a href="https://gist.github.com/melix/ea819c77c4b568660877">micro-benchmark comparing various dynamic languages</a></li>
<li>Cédric Champeau and Jochen Theodorou continue looking into <a href="https://twitter.com/cedricchampeau/status/540117338784530433">optimizations for Groovy</a></li>
<li>The Warsaw Groovy user groups has some <a href="https://twitter.com/szimano/status/540070403268673536">cool Groovy stickers</a>!</li>
<li>The <a href="https://twitter.com/grooscript/status/539898692237795328">GrooScript project celebrates a first pull request</a>!</li>
<li>You can <a href="https://twitter.com/grooscript/status/540492118721298433">try online the GrooScript conversions of Groovy into JavaScript</a></li>
<li>New <a href="https://twitter.com/ratpackweb/status/541908183212490752">documentation on using Ratpack with the Jackson</a> library</li>
<li>Dan Allen believes <a href="https://twitter.com/mojavelinux/status/541554762575929344">Groovy succeeds in making us happy and efficient programmers</a></li>
<li>Sean Gilligan made <a href="https://twitter.com/msgilligan/status/539295258962436097">Groovy hello world work natively on Mac OS X</a> from Cédric Champeau&rsquo;s experiments with RoboVM</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>BloomHealth is <a href="https://twitter.com/shoemaker/status/540187482013573120">hiring Groovy + Angular.JS + Docker fans</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Schalk Cronjé <a href="https://twitter.com/ysb33r/status/540415402246172672">reminds us the Call for Papers for Greach and GR8Conf are still open</a></li>
<li>You can <a href="http://groovy.329449.n5.nabble.com/Ann-Greach-You-can-buy-your-tickets-td5721819.html">buy your ticket for the Greach conference at an early bird price</a> till January 15th</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #47</title><link>https://glaforge.dev/posts/2014/12/02/groovy-weekly-47/</link><pubDate>Tue, 02 Dec 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/12/02/groovy-weekly-47/</guid><description>&lt;p>Today I highly recommend you read about the nice &lt;a href="http://www.elasticsearch.org/blog/making-elasticsearch-groovy-er/">Groovy dedicated support for ElasticSearch&lt;/a>! We also have a few interesting dot releases this week. And there’s the scoop that there’s a Ratpack book in the works!&lt;/p>
&lt;p>Don’t forget that the Call for Papers for GR8Conf Europe / US and Greach are still open, if you want to speak about your favorite Groovy technologies and experiments!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://glaforge.dev/posts/2014/11/26/groovy-2-4-0-beta-4/">Groovy 2.4.0-beta-4&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://glaforge.dev/posts/2014/11/28/groovy-2-3-8-released-too/">Groovy 2.3.8&lt;/a> released&lt;/li>
&lt;li>&lt;a href="http://www.ratpack.io/versions/0.9.11">Ratpack 0.9.11&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/_yoav_/status/539551753742471168">Gradle Bintray plugin 1.0&lt;/a> released with Maven Central sync&lt;/li>
&lt;li>A much faster &lt;a href="https://twitter.com/davydotcom/status/539478433445994496">Asset Pipeline Gradle plugin with v2.0.4&lt;/a>&lt;/li>
&lt;li>First public version of an &lt;a href="https://twitter.com/gernotstarke/status/539441261531660289">HTML sanity checker Gradle plugin&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://plus.google.com/b/113675159854671799959/+AlBakerDev/posts/9tMes6wcaTX?cfem=1">GroovySparql 0.9&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>&amp;ldquo;&lt;a href="http://www.elasticsearch.org/blog/making-elasticsearch-groovy-er/">Making ElasticSearch Groovy-er&lt;/a>&amp;rdquo; with a dedicated Groovy client to interact with ElasticSearch&lt;/li>
&lt;li>Jochen &amp;ldquo;blackdrag&amp;rdquo; Theodorou experiments with Java&amp;rsquo;s processing API for a &lt;a href="http://blackdragsview.blogspot.fr/2014/11/a-joint-compiler-for-groovy-and-java.html">stub-less Groovy joint compiler&lt;/a>&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Grails goodness: &lt;a href="http://mrhaki.blogspot.fr/2014/11/grails-goodness-create-new-application.html">create new application without wrapper&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://eshepelyuk.github.io/2014/11/26/-testing-jvm-javascript-jasmine-spock-nashorn.html">Testing JVM server-side JavaScript with Jasmine, Spock and Nashorn&lt;/a>, by Evgeny Shepelyuk&lt;/li>
&lt;li>Series of articles on &lt;a href="http://www.petrikainulainen.net/getting-started-with-gradle/">getting started with Gradle&lt;/a>&lt;/li>
&lt;li>In this interview, JetBrains developer Maxim Medvedev mentions that &lt;a href="http://blog.jetbrains.com/objc/2014/11/cooking-swift-an-interview-with-maxim-medvedev/">Groovy and Apple Swift are similar in terms of IDE support&lt;/a>, as both languages have type inference, closures, and higher order functions&lt;/li>
&lt;li>On &lt;a href="https://jkschneider.github.io/blog/2014/recursive-observables-with-rxjava.html">recursive observables with RxJava, and showing how Groovy can cut verbosity&lt;/a> down&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/madridgug/status/537944334008872960">Android development with Groovy&lt;/a>, by Mario García, at CodeMotion Spain 2014&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://grydeske.net/news/show/72">Grails Diary week 48&lt;/a> by Jacob Aae Mikkelsen&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau created a &lt;a href="https://gist.github.com/melix/e4b63fd684e63713c162">Docker container with GVM and Groovy&lt;/a> installed&lt;/li>
&lt;li>A new &lt;a href="http://grooscript.org/demo/snapsvg.html">GrooScript demo with snap.svg&lt;/a>&lt;/li>
&lt;li>Using &lt;a href="https://gist.github.com/chiquitinxx/5192ff81d3b3351e3f1d">Firebase from your web pages with Groovy with GrooScript&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau teases for his Groovy Grails eXchange talk where he&amp;rsquo;ll explain how &lt;a href="https://twitter.com/cedricchampeau/status/538261967488974848">Groovy leverages Gradle, Asciidoctor, TeamCity for the new website&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/537882370066874369">Groovy 2.4 beta-4&lt;/a> available through GVM&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/cedricchampeau/status/537883662633959424">GVM announcement of the availability of Groovy 2.4 beta-4 was done from Groovy&amp;rsquo;s CI server&lt;/a> through GVM&amp;rsquo;s API&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/537874472888705024">Gradle 2.2.1&lt;/a> available on GVM&lt;/li>
&lt;li>Bertrand Goetzman spotted a &lt;a href="https://twitter.com/bgoetzmann/status/537363461832196097">Yeoman generator for Angular.JS + Ratpack&lt;/a> applications&lt;/li>
&lt;li>Tomas Lin suggests a &lt;a href="https://twitter.com/tomaslin/status/537436124189175809">Ratatouille inspired book cover for Dan Woods&amp;rsquo; upcoming Ratpack book&lt;/a>&lt;/li>
&lt;li>Eugene Kamenev is showing a screenshot of an &lt;a href="https://twitter.com/eugenekamenev/status/537664752621989889">Android Wear application written in Groovy&lt;/a>&lt;/li>
&lt;li>Roberto Guerra is having fun with &lt;a href="https://twitter.com/robertoguerra19/status/538048345529663489">Ratpack and Gulp.JS&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/538269325959106561">Groovy 2.3.8&lt;/a> has been released on GVM&lt;/li>
&lt;li>Dierk König is looking forward to using Tim Yates&amp;rsquo; &lt;a href="https://twitter.com/mittie/status/539056498073407488">groovy-stream library&lt;/a> more&lt;/li>
&lt;li>Jacob Aae Mikkelsen will be &lt;a href="https://twitter.com/JacobAae/status/538115269034774528">teaching web development with Groovy and Grails at the University of Southern Denmark&lt;/a>&lt;/li>
&lt;li>Rob Fletcher points at &lt;a href="https://twitter.com/rfletcherEW/status/538283579978317825">Gdub a tool that transparently uses gradle or gradlew when available&lt;/a>&lt;/li>
&lt;li>The Grain framework now features some &lt;a href="https://twitter.com/grainframework/status/539409491197173760">stylish bootstrap based templates&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="book">Book&lt;/h2>
&lt;ul>
&lt;li>Dan Woods will write a &lt;a href="https://twitter.com/danveloper/status/537277382571610112">book on Ratpack&lt;/a> for O&amp;rsquo;Reilly&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/greachconf/status/537548869429522433">Autentia becomes gold sponsor for the Greach&lt;/a> conference&lt;/li>
&lt;li>&lt;a href="https://twitter.com/greachconf/status/538273658494406656">Pronoid becomes gold sponsor of Greach&lt;/a>&lt;/li>
&lt;li>Remember that the &lt;a href="https://twitter.com/greachconf/status/538271091085418496">Call for Paper of the Greach conference is still open&lt;/a>!&lt;/li>
&lt;li>You can &lt;a href="https://twitter.com/greachconf/status/539356504244367360">buy tickets for the Greach&lt;/a> conference now!&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/gr8conf/status/539577626185052160">GR8Conf Europe and GR8Conf US Call for Paper&lt;/a> is still open&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Today I highly recommend you read about the nice <a href="http://www.elasticsearch.org/blog/making-elasticsearch-groovy-er/">Groovy dedicated support for ElasticSearch</a>! We also have a few interesting dot releases this week. And there’s the scoop that there’s a Ratpack book in the works!</p>
<p>Don’t forget that the Call for Papers for GR8Conf Europe / US and Greach are still open, if you want to speak about your favorite Groovy technologies and experiments!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://glaforge.dev/posts/2014/11/26/groovy-2-4-0-beta-4/">Groovy 2.4.0-beta-4</a> released</li>
<li><a href="https://glaforge.dev/posts/2014/11/28/groovy-2-3-8-released-too/">Groovy 2.3.8</a> released</li>
<li><a href="http://www.ratpack.io/versions/0.9.11">Ratpack 0.9.11</a> released</li>
<li><a href="https://twitter.com/_yoav_/status/539551753742471168">Gradle Bintray plugin 1.0</a> released with Maven Central sync</li>
<li>A much faster <a href="https://twitter.com/davydotcom/status/539478433445994496">Asset Pipeline Gradle plugin with v2.0.4</a></li>
<li>First public version of an <a href="https://twitter.com/gernotstarke/status/539441261531660289">HTML sanity checker Gradle plugin</a></li>
<li><a href="https://plus.google.com/b/113675159854671799959/+AlBakerDev/posts/9tMes6wcaTX?cfem=1">GroovySparql 0.9</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>&ldquo;<a href="http://www.elasticsearch.org/blog/making-elasticsearch-groovy-er/">Making ElasticSearch Groovy-er</a>&rdquo; with a dedicated Groovy client to interact with ElasticSearch</li>
<li>Jochen &ldquo;blackdrag&rdquo; Theodorou experiments with Java&rsquo;s processing API for a <a href="http://blackdragsview.blogspot.fr/2014/11/a-joint-compiler-for-groovy-and-java.html">stub-less Groovy joint compiler</a></li>
<li>MrHaki&rsquo;s Grails goodness: <a href="http://mrhaki.blogspot.fr/2014/11/grails-goodness-create-new-application.html">create new application without wrapper</a></li>
<li><a href="http://eshepelyuk.github.io/2014/11/26/-testing-jvm-javascript-jasmine-spock-nashorn.html">Testing JVM server-side JavaScript with Jasmine, Spock and Nashorn</a>, by Evgeny Shepelyuk</li>
<li>Series of articles on <a href="http://www.petrikainulainen.net/getting-started-with-gradle/">getting started with Gradle</a></li>
<li>In this interview, JetBrains developer Maxim Medvedev mentions that <a href="http://blog.jetbrains.com/objc/2014/11/cooking-swift-an-interview-with-maxim-medvedev/">Groovy and Apple Swift are similar in terms of IDE support</a>, as both languages have type inference, closures, and higher order functions</li>
<li>On <a href="https://jkschneider.github.io/blog/2014/recursive-observables-with-rxjava.html">recursive observables with RxJava, and showing how Groovy can cut verbosity</a> down</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="https://twitter.com/madridgug/status/537944334008872960">Android development with Groovy</a>, by Mario García, at CodeMotion Spain 2014</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://grydeske.net/news/show/72">Grails Diary week 48</a> by Jacob Aae Mikkelsen</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau created a <a href="https://gist.github.com/melix/e4b63fd684e63713c162">Docker container with GVM and Groovy</a> installed</li>
<li>A new <a href="http://grooscript.org/demo/snapsvg.html">GrooScript demo with snap.svg</a></li>
<li>Using <a href="https://gist.github.com/chiquitinxx/5192ff81d3b3351e3f1d">Firebase from your web pages with Groovy with GrooScript</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau teases for his Groovy Grails eXchange talk where he&rsquo;ll explain how <a href="https://twitter.com/cedricchampeau/status/538261967488974848">Groovy leverages Gradle, Asciidoctor, TeamCity for the new website</a></li>
<li><a href="https://twitter.com/gvmtool/status/537882370066874369">Groovy 2.4 beta-4</a> available through GVM</li>
<li>The <a href="https://twitter.com/cedricchampeau/status/537883662633959424">GVM announcement of the availability of Groovy 2.4 beta-4 was done from Groovy&rsquo;s CI server</a> through GVM&rsquo;s API</li>
<li><a href="https://twitter.com/gvmtool/status/537874472888705024">Gradle 2.2.1</a> available on GVM</li>
<li>Bertrand Goetzman spotted a <a href="https://twitter.com/bgoetzmann/status/537363461832196097">Yeoman generator for Angular.JS + Ratpack</a> applications</li>
<li>Tomas Lin suggests a <a href="https://twitter.com/tomaslin/status/537436124189175809">Ratatouille inspired book cover for Dan Woods&rsquo; upcoming Ratpack book</a></li>
<li>Eugene Kamenev is showing a screenshot of an <a href="https://twitter.com/eugenekamenev/status/537664752621989889">Android Wear application written in Groovy</a></li>
<li>Roberto Guerra is having fun with <a href="https://twitter.com/robertoguerra19/status/538048345529663489">Ratpack and Gulp.JS</a></li>
<li><a href="https://twitter.com/gvmtool/status/538269325959106561">Groovy 2.3.8</a> has been released on GVM</li>
<li>Dierk König is looking forward to using Tim Yates&rsquo; <a href="https://twitter.com/mittie/status/539056498073407488">groovy-stream library</a> more</li>
<li>Jacob Aae Mikkelsen will be <a href="https://twitter.com/JacobAae/status/538115269034774528">teaching web development with Groovy and Grails at the University of Southern Denmark</a></li>
<li>Rob Fletcher points at <a href="https://twitter.com/rfletcherEW/status/538283579978317825">Gdub a tool that transparently uses gradle or gradlew when available</a></li>
<li>The Grain framework now features some <a href="https://twitter.com/grainframework/status/539409491197173760">stylish bootstrap based templates</a></li>
</ul>
<h2 id="book">Book</h2>
<ul>
<li>Dan Woods will write a <a href="https://twitter.com/danveloper/status/537277382571610112">book on Ratpack</a> for O&rsquo;Reilly</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/greachconf/status/537548869429522433">Autentia becomes gold sponsor for the Greach</a> conference</li>
<li><a href="https://twitter.com/greachconf/status/538273658494406656">Pronoid becomes gold sponsor of Greach</a></li>
<li>Remember that the <a href="https://twitter.com/greachconf/status/538271091085418496">Call for Paper of the Greach conference is still open</a>!</li>
<li>You can <a href="https://twitter.com/greachconf/status/539356504244367360">buy tickets for the Greach</a> conference now!</li>
<li>The <a href="https://twitter.com/gr8conf/status/539577626185052160">GR8Conf Europe and GR8Conf US Call for Paper</a> is still open</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.3.8 released too</title><link>https://glaforge.dev/posts/2014/11/28/groovy-2-3-8-released-too/</link><pubDate>Fri, 28 Nov 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/11/28/groovy-2-3-8-released-too/</guid><description>&lt;p>Hot on the heels of our &lt;a href="https://glaforge.dev/posts/2014/11/26/groovy-2-4-0-beta-4/">new beta for 2.4&lt;/a>, here&amp;rsquo;s a bug fix release with Groovy 2.3.8.&lt;/p>
&lt;p>You can have a look at the &lt;a href="http://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;amp;version=20648">JIRA release&lt;/a> and you can &lt;a href="http://beta.groovy-lang.org/download.html">download Groovy 2.3.8&lt;/a> right away!&lt;/p>
&lt;p>Thanks all for your contributions!&lt;/p>
&lt;p>Keep on groovy&amp;rsquo;ing!&lt;/p></description><content:encoded>
<![CDATA[<p>Hot on the heels of our <a href="https://glaforge.dev/posts/2014/11/26/groovy-2-4-0-beta-4/">new beta for 2.4</a>, here&rsquo;s a bug fix release with Groovy 2.3.8.</p>
<p>You can have a look at the <a href="http://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20648">JIRA release</a> and you can <a href="http://beta.groovy-lang.org/download.html">download Groovy 2.3.8</a> right away!</p>
<p>Thanks all for your contributions!</p>
<p>Keep on groovy&rsquo;ing!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.4.0-beta-4</title><link>https://glaforge.dev/posts/2014/11/26/groovy-2-4-0-beta-4/</link><pubDate>Wed, 26 Nov 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/11/26/groovy-2-4-0-beta-4/</guid><description>&lt;p>This is with great pleasure that we are announcing the release of Groovy 2.4.0-beta-4.&lt;/p>
&lt;p>The highlights for this release are:&lt;/p>
&lt;ul>
&lt;li>a rewritten JsonBuilder for improved performance in JSON generation&lt;/li>
&lt;li>a &lt;a href="http://docs.groovy-lang.org/2.4.0-beta-4/html/documentation/core-traits.html#_self_types">&lt;code>@SelfType&lt;/code>&lt;/a> annotation for traits&lt;/li>
&lt;li>a new &lt;a href="http://docs.groovy-lang.org/2.4.0-beta-4/html/gapi/groovy/text/StreamingTemplateEngine.html">variant of &lt;code>GStringTemplateEngine&lt;/code>&lt;/a> capable of handling strings larger than 64k&lt;/li>
&lt;li>improved support for overloaded setters&lt;/li>
&lt;li>lots of bugfixes (some of which are backported in the upcoming 2.3.8 release)&lt;/li>
&lt;li>a new naming convention for closures&lt;/li>
&lt;/ul>
&lt;p>The last point is important if your project somehow relies on the name of the closure classes as it is a potential breaking change. The reason for the new scheme is detailed &lt;a href="https://jira.codehaus.org/browse/GROOVY-5351">here&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>This is with great pleasure that we are announcing the release of Groovy 2.4.0-beta-4.</p>
<p>The highlights for this release are:</p>
<ul>
<li>a rewritten JsonBuilder for improved performance in JSON generation</li>
<li>a <a href="http://docs.groovy-lang.org/2.4.0-beta-4/html/documentation/core-traits.html#_self_types"><code>@SelfType</code></a> annotation for traits</li>
<li>a new <a href="http://docs.groovy-lang.org/2.4.0-beta-4/html/gapi/groovy/text/StreamingTemplateEngine.html">variant of <code>GStringTemplateEngine</code></a> capable of handling strings larger than 64k</li>
<li>improved support for overloaded setters</li>
<li>lots of bugfixes (some of which are backported in the upcoming 2.3.8 release)</li>
<li>a new naming convention for closures</li>
</ul>
<p>The last point is important if your project somehow relies on the name of the closure classes as it is a potential breaking change. The reason for the new scheme is detailed <a href="https://jira.codehaus.org/browse/GROOVY-5351">here</a>.</p>
<p>You can download Groovy in our download area:
<a href="http://beta.groovy-lang.org/download.html">http://beta.groovy-lang.org/download.html</a></p>
<p>You can consult the JIRA change log:
<a href="http://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20612">http://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20612</a></p>
<p>Thanks for your feedback and contributions!</p>
<p>Note that we are likely to change the scope of the 2.4 to exclude macros. There’s still a lot of work and discussion to be done on that topic, making it incompatible with our target for a 2.4 release. This feature is likely to be delayed to Groovy 2.5.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #46</title><link>https://glaforge.dev/posts/2014/11/25/groovy-weekly-46/</link><pubDate>Tue, 25 Nov 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/11/25/groovy-weekly-46/</guid><description>&lt;p>My favorite read of the week is definitely Ken Kousen’s tale about Groovy’s &lt;a href="http://www.accelebrate.com/blog/call-pogo-name/">Plain Old Groovy Object&lt;/a>! Note the releases of the new Groovy Eclipse support and the Eclipse Maven batch compiler. Last but not least, for those of you heading to the &lt;a href="https://skillsmatter.com/conferences/1957-groovy-grails-exchange-2014">Groovy &amp;amp; Grails eXchange conference in London&lt;/a>, notice the &lt;a href="https://skillsmatter.com/conferences/1957-groovy-grails-exchange-2014#program">program&lt;/a> of the conference unfolding before your eyes!&lt;/p>
&lt;p>Let me also mention the &lt;a href="http://beakernotebook.com/index">Beaker notebook project&lt;/a>, which offers Groovy support for data scientists who want to explore their data sets. And the &lt;a href="https://twitter.com/virtualkenji/status/535280638580887553">Spoon.net&lt;/a> team which features Groovy support for its Windows containers.&lt;/p></description><content:encoded>
<![CDATA[<p>My favorite read of the week is definitely Ken Kousen’s tale about Groovy’s <a href="http://www.accelebrate.com/blog/call-pogo-name/">Plain Old Groovy Object</a>! Note the releases of the new Groovy Eclipse support and the Eclipse Maven batch compiler. Last but not least, for those of you heading to the <a href="https://skillsmatter.com/conferences/1957-groovy-grails-exchange-2014">Groovy &amp; Grails eXchange conference in London</a>, notice the <a href="https://skillsmatter.com/conferences/1957-groovy-grails-exchange-2014#program">program</a> of the conference unfolding before your eyes!</p>
<p>Let me also mention the <a href="http://beakernotebook.com/index">Beaker notebook project</a>, which offers Groovy support for data scientists who want to explore their data sets. And the <a href="https://twitter.com/virtualkenji/status/535280638580887553">Spoon.net</a> team which features Groovy support for its Windows containers.</p>
<p>Final word, don’t miss Graeme’s tweets pointing at the Grails 3 scaffolding plugin and code generation APIs!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://github.com/groovy/groovy-eclipse/wiki/Groovy-Eclipse-2.9.1-Release-Notes">Groovy Eclipse 2.9.1</a> released</li>
<li><a href="http://groovy.329449.n5.nabble.com/Groovy-Eclipse-Maven-Batch-comiler-2-3-7-release-candidate-available-for-testing-td5721719.html">Groovy Eclipse Maven Batch comiler 2.3.7</a> release candidate available for testing</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-2-1-released">Gradle 2.2.1</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>An awesome tale about POGOs by Ken Kousen: &ldquo;<a href="http://www.accelebrate.com/blog/call-pogo-name/">That Which We Call A POGO, By Any Other Name</a>&rdquo;</li>
<li><a href="http://www.oodlestechnologies.com/blogs/How-to-use-Groovy-Traits">How to use Groovy traits</a> by Rohan Jain</li>
<li><a href="https://solidsoft.wordpress.com/2014/11/19/gradle-tricks-tracking-down-not-expected-transitive-dependencies/">Tracking down not expected transitive dependencies in multi-project Gradle build</a> by Marcin Zajączkowski</li>
<li>Craig Burke publishes the second part on <a href="http://www.craigburke.com/2014/11/24/angular-grails-template-2.html">advanced customization of the Angular.JS Grails template</a></li>
<li>MrHaki&rsquo;s Gradle goodness: <a href="http://mrhaki.blogspot.fr/2014/11/gradle-goodness-using-and-working-with.html">Using and working with Gradle version</a></li>
<li>MrHaki&rsquo;s Gradle goodness: <a href="http://mrhaki.blogspot.fr/2014/11/gradle-goodness-using-copyspec-with.html">Using CopySpeck with Tasks</a></li>
<li>How to use <a href="http://www.oodlestechnologies.com/blogs/How-to-use-custom-tags-in-grails-controller%2C-services-groovy-file">custom tags in Grails controllers and services</a> by Ankush Kocher</li>
<li>How to <a href="http://aruizca.com/how-to-render-json-properly-without-escaping-quotes-inside-a-gsp-script-tag/">render JSON properly inside a GSP script tag</a> by Angel Ruiz</li>
<li><a href="http://www.intelligrape.com/blog/2014/11/19/object-based-security-using-spring-security-acl-in-grails/">Object based security using Spring Security ACL in Grails</a> by Anil Agrawal</li>
<li>Ames Lorenzen shares an <a href="http://java.dzone.com/articles/example-using-grails-promises">example using Grails promises</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Cédric Champeau presented in French a quick <a href="https://speakerdeck.com/melix/human-talks-nayez-pas-peur-de-faire-du-groovy">introduction to Groovy and its powers</a>, and the slides are quite self-explanatory even if you don&rsquo;t speak French!</li>
<li>A quick <a href="http://fr.slideshare.net/Ciklum_Kyiv/groovy-on-android">intro and comparison between Groovy and Apple Swift</a> in the context of mobile development with Android, by Olexandr Leuschenko</li>
<li><a href="https://twitter.com/alsargent/status/536019492204068864">Scalable Big Data stream processing with Apache Storm and Groovy</a> by Eugene Dvorkin, at SpringOne2GX 2014</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Dan Woods is preparing a <a href="http://www.oreilly.com/pub/e/3275">webcast about Ratpack</a> for O&rsquo;Reilly</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/71">Grails Diary</a> week 47</li>
<li>The <a href="http://beakernotebook.com/index">data scientist&rsquo;s laboratory, Beaker, offers support for Groovy</a> for working with large and complex datasets</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Guillaume Laforge mentions on twitter a <a href="https://twitter.com/glaforge/status/535518780185903104">telescope project using Groovy</a></li>
<li>Graeme Rocher worked on the <a href="https://twitter.com/graemerocher/status/537184433489866753">first cut of the new Grails 3 scaffolding plugin</a> with the new code generation APIs</li>
<li>Graeme Rocher shows <a href="https://twitter.com/graemerocher/status/537185286456115200">how to implement a codegen script for Grails 3</a></li>
<li>Craig Burke is working on a <a href="https://twitter.com/craigburke1/status/535452259363356673">Word and PDF Groovy builder</a></li>
<li>Luke Daley launches a rumor about <a href="https://twitter.com/ldaley/status/535499624682119170">Dan Woods working on a Ratpack book</a> for O&rsquo;Reilly</li>
<li>Rob Fletcher <a href="https://twitter.com/rfletcherew/status/535426936974368768">appreciates Groovy even more when forced to write Java</a> code</li>
<li>Andrés Almiray releases v0.1 of the <a href="https://twitter.com/gradleplugins/status/536270638898774017">Clirr Gradle plugin for checking binary compatibility between different API versions</a></li>
<li>Dan Woods tells anyone <a href="https://twitter.com/danveloper/status/534857082965880832">excited by the idea of a Java REPL that there&rsquo;s already one available, Groovy&rsquo;s one!</a></li>
<li>Peter Ledbrook says <a href="https://twitter.com/pledbrook/status/535094362645233665">Groovy Grails eXchange will feature a beginner track</a> on the first day and a full day hacking session on the second</li>
<li>Jacek Laskowski notes that the <a href="https://twitter.com/jaceklaskowski/status/536889293307146240">Gremlin terminal is a Groovy shell</a></li>
<li>Etienne Studer releases a <a href="https://twitter.com/etiennestuder/status/536696709112283136">Gradle plugin to help plugin authors with bundling and publishing of Gradle plugins</a></li>
<li><a href="https://twitter.com/gvmtool/status/536066336850735104">Spring Boot 1.2-rc-2</a> is available on GVM</li>
<li>Ryan Vanderwerf is looking forward <a href="https://twitter.com/RyanVanderwerf/status/535911468424638464">programming in Groovy on his Xmas Android smartwatch</a></li>
<li>Bruno Borges announces Groo<a href="https://twitter.com/brunoborges/status/535999133254184960">vy support for scripted Java FX on webservers</a></li>
<li>Erik Pragt is working on the <a href="https://twitter.com/epragt/status/536091467153162240">IntelliJ IDEA support for Asciidoctor and shows a screenshot of the Groovy documentation</a> as an example</li>
<li>Iván López spoke about <a href="https://twitter.com/ilopmar/status/536115353815105536">Groovy on Android to a packed room at the CodeMotion</a> conference</li>
<li>Burt Beckwith shares a tip for those who can&rsquo;t use Spock: <a href="https://twitter.com/burtbeckwith/status/536716841737916416">add a static import on GroovyAssert#shouldFail to JUnit 4 tests</a> to follow the same pattern as in JUnit 3</li>
<li>Spoon.net releases a <a href="https://twitter.com/virtualkenji/status/535280638580887553">Groovy image for its Windows container engine</a></li>
<li>Craig Burke says that <a href="https://twitter.com/craigburke1/status/535112280703516673">reading on functional programming in Java 8 makes him appreciate Groovy closures</a> even more</li>
</ul>
<h2 id="book">Book</h2>
<ul>
<li>Schalk Cronjé is looking for <a href="https://twitter.com/ysb33r/status/535574251114598400">feedback on smart techniques used within your Gradle plugins for contributions for a Gradle book</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="http://www.meetup.com/virtualJUG/events/218827084/?gj=rcs.a&amp;a=co2.a_grp&amp;rv=rcs.a">Guillaume Laforge will be speaking at the Virtual JUG</a></li>
<li>The <a href="https://twitter.com/Gr8Ladies/status/534886675424755712">December GR8 Ladies meetup</a> is sponsored by BloomHealth and AgileOrbit</li>
<li><a href="https://twitter.com/gr8ladiesmsp/status/535499595175182336">Object Partners becomes platinum sponsor of the GR8 Ladies</a> December GR8 workshop</li>
<li>The <a href="https://skillsmatter.com/conferences/1957-groovy-grails-exchange-2014#program">Groovy &amp; Grails eXchange conference agenda</a> is uncovering</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #45</title><link>https://glaforge.dev/posts/2014/11/18/groovy-weekly-45/</link><pubDate>Tue, 18 Nov 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/11/18/groovy-weekly-45/</guid><description>&lt;p>This week, coming back from the Devoxx conference in Belgium, I’ve gathered a bag of Groovy news for your consumption, for our usual Tuesday Groovy Weekly release!&lt;/p>
&lt;p>No particular big highlight today, but perhaps just a happy birthday to GrooScript, the Groovy to JavaScript transpiler, and also notice the opening of the Call for Paper for the GR8Conf conferences in Europe and in the United States.&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://forums.gradle.org/gradle/topics/gradle-2-2-1-rc-1-is-now-available-for-testing">Gradle 2.2.1-rc-1&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Luke Daley explains &lt;a href="http://ldaley.com/post/102495950257/ratpacks-execution-model-in-practice">Ratpack&amp;rsquo;s execution model in practice&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://grysz.com/2014/11/14/custom-collections-in-groovy/">Custom collections in Groovy&lt;/a> by Marcin Gryszko&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Gradle Goodness: &lt;a href="http://mrhaki.blogspot.fr/2014/11/gradle-goodness-check-task-dependencies.html">Check task dependencies with a dry run&lt;/a>&lt;/li>
&lt;li>Dan Tanner wrote about the &lt;a href="http://otherthanthink.blogspot.fr/2014/11/groovy-collect-vs-spread-dot-operator.html">differences between Groovy&amp;rsquo;s collect and spread-dot operator&lt;/a>&lt;/li>
&lt;li>Displaying &lt;a href="https://solidsoft.wordpress.com/2014/11/13/gradle-tricks-display-dependencies-for-all-subprojects-in-multi-project-build/">all dependencies for all subprojects in a multi-project Gradle build&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.objectpartners.com/2014/11/11/stringtemplates-in-groovy/">String templates in Groovy&lt;/a> by Mike Hostetler&lt;/li>
&lt;li>Craig Burke writes the first article of a series about the &lt;a href="http://www.craigburke.com/2014/11/17/angular-grails-template-1.html">Angular.JS Grails template&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>Devoxx special
&lt;ul>
&lt;li>Guillaume Laforge presented &amp;ldquo;&lt;a href="https://speakerdeck.com/glaforge/groovy-in-the-light-of-java-8-devoxx-2014">Groovy in the light of Java 8&lt;/a>&amp;rdquo;&lt;/li>
&lt;li>Guillaume Laforge presented &amp;ldquo;&lt;a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-devoxx-2014">Groovy in 2014 and beyond&lt;/a>&amp;rdquo;&lt;/li>
&lt;li>Cédric Champeau presented &amp;ldquo;&lt;a href="https://speakerdeck.com/melix/groovy-and-android-a-winning-pair-1">Groovy and Android, the winning pair&lt;/a>&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Jérôme Louvel from the Restlet and APISpark projects mentions a &lt;a href="https://twitter.com/jlouvel/status/532699896600477696">PayPal API platform presentation showing Groovy being used for API composition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-lists">Mailing-lists&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://groovy.329449.n5.nabble.com/New-version-of-groovy-eclipse-compiler-td5721689.html">New versions of the Groovy Eclipse compiler&lt;/a> are coming along&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>An update of &lt;a href="https://twitter.com/groovyblogs/status/533146828275859456">GroovyBlogs with infinite loading of recent and popular blog posts&lt;/a>&lt;/li>
&lt;li>Andrés Almiray found this old gem in honor of Dick Wall from the Javaposse fame as the podcast comes to its end, showing &lt;a href="http://blog.cliffano.com/2009/03/02/lovin-the-groovy/">Dick loving the Groovy&lt;/a>&lt;/li>
&lt;li>Jacob Aae Mikkelsen&amp;rsquo;s &lt;a href="http://grydeske.net/news/show/70">Grails Diary week 46&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/533687966951297026">GrooScript celebrates 2 years since the 0.1 release&lt;/a>!&lt;/li>
&lt;li>Lari Hotari is working on &lt;a href="https://twitter.com/lhotari/status/534082054527672320">Grails 3 / Spring Boot startup time optimisations&lt;/a>&lt;/li>
&lt;li>Patrick Double published a &lt;a href="https://twitter.com/DoubleCreekNE/status/532295990586658816">Grails plugin to improve Grails / SauceLabs Geb integration&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/532619382703521792">GrooScript chat application deployed on Heroku&lt;/a> with Node.JS and Socket.IO&lt;/li>
&lt;li>Ratpack features a &lt;a href="https://twitter.com/ratpackweb/status/532631677411917826">URL builder&lt;/a> utility&lt;/li>
&lt;li>Hans Dockter showed &lt;a href="https://twitter.com/CedricChampeau/status/532841402733178880">declarative dependency replacement with Gradle&lt;/a> at Devoxx 2014&lt;/li>
&lt;li>Hans Dockter showed how to &lt;a href="https://twitter.com/CedricChampeau/status/532842212581978112">tell Gradle that you don&amp;rsquo;t want to depend on beta dependencies&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/534458011893633025">Gradle 2.2.1-rc-1&lt;/a> available on GVM&lt;/li>
&lt;li>Álvaro Sánchez sparks a discussion about &lt;a href="https://twitter.com/alvaro_sanchez/status/532477553576271872">Spock labels indentation&lt;/a> on twitter&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau is working on &lt;a href="https://gist.github.com/melix/6875e12077908129f400">AST tree matching&lt;/a>&lt;/li>
&lt;li>Vladimir Orany shares a snippet showing how to &lt;a href="https://gist.github.com/musketyr/611cacb57639dcaa6b94">enable DSL support in IntelliJ IDEA&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>The &lt;a href="http://us4.campaign-archive2.com/?u=ac7af4c02d6cec67fe3198a63&amp;amp;id=60cfa3d0b4&amp;amp;e=88c97e251c">Call for Paper for GR8Conf Europe and GR8Conf US is open&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>This week, coming back from the Devoxx conference in Belgium, I’ve gathered a bag of Groovy news for your consumption, for our usual Tuesday Groovy Weekly release!</p>
<p>No particular big highlight today, but perhaps just a happy birthday to GrooScript, the Groovy to JavaScript transpiler, and also notice the opening of the Call for Paper for the GR8Conf conferences in Europe and in the United States.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-2-1-rc-1-is-now-available-for-testing">Gradle 2.2.1-rc-1</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Luke Daley explains <a href="http://ldaley.com/post/102495950257/ratpacks-execution-model-in-practice">Ratpack&rsquo;s execution model in practice</a></li>
<li><a href="http://grysz.com/2014/11/14/custom-collections-in-groovy/">Custom collections in Groovy</a> by Marcin Gryszko</li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/11/gradle-goodness-check-task-dependencies.html">Check task dependencies with a dry run</a></li>
<li>Dan Tanner wrote about the <a href="http://otherthanthink.blogspot.fr/2014/11/groovy-collect-vs-spread-dot-operator.html">differences between Groovy&rsquo;s collect and spread-dot operator</a></li>
<li>Displaying <a href="https://solidsoft.wordpress.com/2014/11/13/gradle-tricks-display-dependencies-for-all-subprojects-in-multi-project-build/">all dependencies for all subprojects in a multi-project Gradle build</a></li>
<li><a href="http://www.objectpartners.com/2014/11/11/stringtemplates-in-groovy/">String templates in Groovy</a> by Mike Hostetler</li>
<li>Craig Burke writes the first article of a series about the <a href="http://www.craigburke.com/2014/11/17/angular-grails-template-1.html">Angular.JS Grails template</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Devoxx special
<ul>
<li>Guillaume Laforge presented &ldquo;<a href="https://speakerdeck.com/glaforge/groovy-in-the-light-of-java-8-devoxx-2014">Groovy in the light of Java 8</a>&rdquo;</li>
<li>Guillaume Laforge presented &ldquo;<a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-devoxx-2014">Groovy in 2014 and beyond</a>&rdquo;</li>
<li>Cédric Champeau presented &ldquo;<a href="https://speakerdeck.com/melix/groovy-and-android-a-winning-pair-1">Groovy and Android, the winning pair</a>&rdquo;</li>
</ul>
</li>
<li>Jérôme Louvel from the Restlet and APISpark projects mentions a <a href="https://twitter.com/jlouvel/status/532699896600477696">PayPal API platform presentation showing Groovy being used for API composition</a></li>
</ul>
<h2 id="mailing-lists">Mailing-lists</h2>
<ul>
<li><a href="http://groovy.329449.n5.nabble.com/New-version-of-groovy-eclipse-compiler-td5721689.html">New versions of the Groovy Eclipse compiler</a> are coming along</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>An update of <a href="https://twitter.com/groovyblogs/status/533146828275859456">GroovyBlogs with infinite loading of recent and popular blog posts</a></li>
<li>Andrés Almiray found this old gem in honor of Dick Wall from the Javaposse fame as the podcast comes to its end, showing <a href="http://blog.cliffano.com/2009/03/02/lovin-the-groovy/">Dick loving the Groovy</a></li>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/70">Grails Diary week 46</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/533687966951297026">GrooScript celebrates 2 years since the 0.1 release</a>!</li>
<li>Lari Hotari is working on <a href="https://twitter.com/lhotari/status/534082054527672320">Grails 3 / Spring Boot startup time optimisations</a></li>
<li>Patrick Double published a <a href="https://twitter.com/DoubleCreekNE/status/532295990586658816">Grails plugin to improve Grails / SauceLabs Geb integration</a></li>
<li><a href="https://twitter.com/grooscript/status/532619382703521792">GrooScript chat application deployed on Heroku</a> with Node.JS and Socket.IO</li>
<li>Ratpack features a <a href="https://twitter.com/ratpackweb/status/532631677411917826">URL builder</a> utility</li>
<li>Hans Dockter showed <a href="https://twitter.com/CedricChampeau/status/532841402733178880">declarative dependency replacement with Gradle</a> at Devoxx 2014</li>
<li>Hans Dockter showed how to <a href="https://twitter.com/CedricChampeau/status/532842212581978112">tell Gradle that you don&rsquo;t want to depend on beta dependencies</a></li>
<li><a href="https://twitter.com/gvmtool/status/534458011893633025">Gradle 2.2.1-rc-1</a> available on GVM</li>
<li>Álvaro Sánchez sparks a discussion about <a href="https://twitter.com/alvaro_sanchez/status/532477553576271872">Spock labels indentation</a> on twitter</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau is working on <a href="https://gist.github.com/melix/6875e12077908129f400">AST tree matching</a></li>
<li>Vladimir Orany shares a snippet showing how to <a href="https://gist.github.com/musketyr/611cacb57639dcaa6b94">enable DSL support in IntelliJ IDEA</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="http://us4.campaign-archive2.com/?u=ac7af4c02d6cec67fe3198a63&amp;id=60cfa3d0b4&amp;e=88c97e251c">Call for Paper for GR8Conf Europe and GR8Conf US is open</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #44</title><link>https://glaforge.dev/posts/2014/11/11/groovy-weekly-44/</link><pubDate>Tue, 11 Nov 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/11/11/groovy-weekly-44/</guid><description>&lt;p>Today is the celebration of the veteran day / remembrance day in various countries throughout the world. So I’ll start with a big thank you to our ancestors who fought for our freedom!&lt;/p>
&lt;p>This week is also a busy week as a part of the Groovy core team is moving to Antwerpen in Belgium to speak about Groovy at the Devoxx conference.&lt;/p>
&lt;p>My picks of the week, among the news below are: the release of Gradle 2.2, the release of ElasticSearch 1.4 which now standardizes on Groovy for its default scripting language, and the announcement of the dates of the Greach conference in Spain and its call for paper which is open.&lt;/p></description><content:encoded>
<![CDATA[<p>Today is the celebration of the veteran day / remembrance day in various countries throughout the world. So I’ll start with a big thank you to our ancestors who fought for our freedom!</p>
<p>This week is also a busy week as a part of the Groovy core team is moving to Antwerpen in Belgium to speak about Groovy at the Devoxx conference.</p>
<p>My picks of the week, among the news below are: the release of Gradle 2.2, the release of ElasticSearch 1.4 which now standardizes on Groovy for its default scripting language, and the announcement of the dates of the Greach conference in Spain and its call for paper which is open.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://www.elasticsearch.org/blog/elasticsearch-1-4-0-released/">ElasticSearch 1.4.0</a> released with Groovy as the default scripting language</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-2-released">Gradle 2.2</a> released</li>
<li><a href="https://twitter.com/grailsframework/status/530268377080946689">GORM for Cassandra</a> released by Paras Lakhani</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Roberto Guerra is showing how to <a href="http://blog.stumblingoncode.com/posts/2014-11-06-ratpack-guava-eventbus.html">test the Guava EventBus with Spock</a></li>
<li>Ted Naleid on <a href="http://naleid.com/blog/2014/11/10/debugging-grails-forked-mode/">debugging Grails forked mode</a></li>
<li>Søren Berg Glasius details the <a href="http://sbglasius.tumblr.com/post/101934935847/groovyblogs">resurrection of GroovyBlogs</a>, sponsored by the GR8Conf Europe team</li>
<li><a href="http://blog.anynines.com/groovy/">Deploy a Caelyf Groovy app on Anynine&rsquo;s Cloud Foundry</a> platform</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>A screencast of the <a href="http://bertram.d.pr/1fiob">Gradle asset pipeline</a> in action</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/69">Grails Diary week 45</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau highlights the fact that both <a href="https://twitter.com/CedricChampeau/status/530032446415597569">Groovy traits and Markup template engine use custom type checking extensions</a></li>
<li>A new <a href="https://twitter.com/ratpackweb/status/530163157004587010">Ratpack application online for beer fans</a></li>
<li><a href="https://twitter.com/gvmtool/status/531833062699839488">Gradle 2.2</a> available on GVM</li>
<li><a href="https://twitter.com/groovyserv/status/529967998065340418">GroovyServ is now available on Homebrew</a></li>
<li><a href="https://twitter.com/groovyserv/status/531786080404516866">GroovyServ will also be available in the form of an RPM</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>This Spring Boot class shows how to <a href="https://github.com/melix/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateResolver.java#L40-40">create your own template resolver for Groovy&rsquo;s markup template engine</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/greachconf/status/530662229998186496">Greach conference will take place in Madrid on April 10th-11th</a>, and its Call for Paper is open</li>
<li>Paul King will be speaking at Yow 2014 on the <a href="https://twitter.com/paulk_asert/status/530483395457843200">awesome parts of Groovy</a></li>
<li><a href="https://twitter.com/greachconf/status/530323772092710912">Kaleidos is gold sponsor of the Greach conference</a> (whose CfP is open)</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #43</title><link>https://glaforge.dev/posts/2014/11/04/groovy-weekly-43/</link><pubDate>Tue, 04 Nov 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/11/04/groovy-weekly-43/</guid><description>&lt;p>This week, an InfoWorld article lists &lt;a href="http://www.infoworld.com/article/2840235/application-development/9-cutting-edge-programming-languages-worth-learning-next.html">Groovy among the 9 cutting-edge programming language worth learning today&lt;/a>, although it reduces Groovy to a mere Java dynamic scripting language. Are you happy with the investment you made into learning Groovy a while ago?&lt;/p>
&lt;p>We also have some nice releases, like the monthly Ratpack release, or the 1.0 versions of GroovyServ (for starting up your Groovy scripts at light speed), and Gaiden for making nice documentation with Markdown.&lt;/p></description><content:encoded>
<![CDATA[<p>This week, an InfoWorld article lists <a href="http://www.infoworld.com/article/2840235/application-development/9-cutting-edge-programming-languages-worth-learning-next.html">Groovy among the 9 cutting-edge programming language worth learning today</a>, although it reduces Groovy to a mere Java dynamic scripting language. Are you happy with the investment you made into learning Groovy a while ago?</p>
<p>We also have some nice releases, like the monthly Ratpack release, or the 1.0 versions of GroovyServ (for starting up your Groovy scripts at light speed), and Gaiden for making nice documentation with Markdown.</p>
<p>Jorge Franco keeps on writing some cool demos with GrooScript, to write your JavaScript apps in your favorite programming language. He also unveiled the new <a href="http://grooscript.org/">GrooScript</a> website!</p>
<p>In the news section, I’ve also listed a couple refinements coming up in Groovy 2.4, with @DelegatesTo / @ClosureParams annotations alignments and the new @SelfType annotation for traits to restrict trait application.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://kobo.github.io/groovyserv/changelog.html">GroovyServ 1.0</a> released</li>
<li><a href="http://www.ratpack.io/versions/0.9.10">Ratpack 0.9.10</a> released</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-2-rc-2-is-now-available-for-testing?rfm=1">Gradle 2.2-rc-2</a> released fixing a couple of regressions from the previous release candidate</li>
<li><a href="https://twitter.com/grooscript/status/529365793642131457">GrooScript 0.6.2</a> released</li>
<li><a href="https://twitter.com/cedricchampeau/status/528184277306183681">Groovy android plugin for Gradle 0.3.4</a> released with support for product flavors</li>
<li><a href="https://github.com/renatoathaydes/spock-reports">Spock Reports Extension 1.2.1</a> released</li>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Gaiden-1-0-released-td5721608.html">Gaiden 1.0</a> is out, with theme, numbering, wrapper, and others</li>
<li>An updated <a href="https://registry.hub.docker.com/u/mozart/grails/">optimized Grails Docker container</a></li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>InfoWorld reports about <a href="http://www.infoworld.com/article/2840235/application-development/9-cutting-edge-programming-languages-worth-learning-next.html">9 cutting-edge programming language worth learning now, including Groovy</a></li>
<li>Roberto Guerra dives into <a href="http://blog.stumblingoncode.com/posts/2014-10-31-ratpack-guice.html">Ratpack Guice-based modularisation and dependency injection</a></li>
<li>Another great demo of <a href="http://grooscript.org/chat_example.html">Grooscript writing a Node.JS chat application with Groovy and Socket.IO</a></li>
<li>Julien Viet details the <a href="https://github.com/vert-x3/ext/blob/master/ext-rx/README.md">Rx extension for Vert.x and shows the RxGroovy integration</a> too</li>
<li>Patrick Double shares his <a href="http://www.objectpartners.com/2014/10/29/experiences-with-publishing-a-grails-plugin/">experience with publishing a Grails plugin</a></li>
<li>Albert van Veen on <a href="http://blog.jdriven.com/2014/10/grails-generate-async-controller/">Grails&rsquo; generation of asynchronous controllers</a></li>
<li>Iván López shares his tales from the GeeCON conference</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="https://speakerdeck.com/jlstrater/groovy-at-gr8ladies-iowa-code-camp">Groovy introduction</a> by the GR8 Ladies at the Iowa code camp 2014</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Bertrand Goetzmann recorded a screencast on <a href="https://www.youtube.com/watch?v=wSlCWJL0Q5I&amp;feature=youtu.be&amp;a">monitoring an Apache Camel route started from a Groovy script, with the Hawtio web console</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>A <a href="https://twitter.com/grooscript/status/527253669264502784">new look for the GrooScript website</a></li>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/68">Grails Diary</a> week 44</li>
<li>Groovy&rsquo;s <a href="https://jira.codehaus.org/browse/GROOVY-6956">@DelegatesTo and @ClosureParams will be more aligned in Groovy 2.4</a>, the latter gaining the same parameters as the former, for more closure type checking goodness</li>
<li>Groovy 2.4 adds a <a href="https://jira.codehaus.org/browse/GROOVY-7134">@SelfType annotation to traits</a> allowing you to restrict to what types a trait can be applied</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/cedricchampeau/status/529191824796430336">Groovy works well on the new Android runtime ART</a> as well tells Cédric Champeau</li>
<li>Schalk Cronjé tweets that <a href="https://twitter.com/ysb33r/status/528184841951780864">Groovy isn&rsquo;t magical, but is an elixir to better programmer health</a></li>
<li>Robert Fletcher updated the <a href="https://twitter.com/rfletcherew/status/527498052752265217">Gradle Compass plugin</a></li>
<li>Dan Woods <a href="https://twitter.com/danveloper/status/527155055917412352">loves the new Groovy documentation</a></li>
<li><a href="https://twitter.com/gvmtool/status/529403342095925248">Gradle 2.2-rc-2</a> available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/529625692028538880">GroovyServ 1.0</a> is available on GVM</li>
<li>Dierk König <a href="https://twitter.com/mittie/status/528961665292054529">compares GPars dataflow variables to references in pure functional languages</a></li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li>André Steingreß is looking for a <a href="http://findgrailsjobs.com/job/673-software-developer-fm-java-groovy-grails-remote">Grails developer</a> in this job posting</li>
<li>Ben McGuire is <a href="https://twitter.com/ben_t_mcguire/status/527812020607787008">looking for Grails developers</a> for a major project in San Diego</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>GrailsConf India has opened its <a href="http://grailsconf.in/call-for-paper">Call for Paper</a></li>
<li>Cédric Champeau will be speaking about <a href="https://twitter.com/cedricchampeau/status/527443577388142592">Groovy for Android at Mobile Central Europe</a>, Warsaw, in February</li>
<li>Iván López is speaking about the <a href="https://twitter.com/ilopmar/status/527832608835923968">Grails framework at jDays 2015</a> in Gothenburg next March</li>
<li>The <a href="https://twitter.com/gr8ladiesmsp/status/529316231459778560">next GR8 Ladies meetup will be November 17th</a> if you&rsquo;re in the Minneapolis area</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #42</title><link>https://glaforge.dev/posts/2014/10/28/groovy-weekly-42/</link><pubDate>Tue, 28 Oct 2014 00:00:00 +0100</pubDate><guid>https://glaforge.dev/posts/2014/10/28/groovy-weekly-42/</guid><description>&lt;p>Isn’t it a mythical number? The 42nd edition of Groovy Weekly! Will we get the answer to life the universe and everything?&lt;/p>
&lt;p>At least, we have a nice list of releases, this week, in particular with Grails 2.4.4, Geb 0.10 and the first milestone of Reactor 2.0!&lt;/p>
&lt;p>And if there’s one thing I’d like to highlight in addition, that would be MrHaki’s release of his new book, the Spock Notebook, with all those nice recipes about our preferred testing framework.&lt;/p></description><content:encoded>
<![CDATA[<p>Isn’t it a mythical number? The 42nd edition of Groovy Weekly! Will we get the answer to life the universe and everything?</p>
<p>At least, we have a nice list of releases, this week, in particular with Grails 2.4.4, Geb 0.10 and the first milestone of Reactor 2.0!</p>
<p>And if there’s one thing I’d like to highlight in addition, that would be MrHaki’s release of his new book, the Spock Notebook, with all those nice recipes about our preferred testing framework.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grailsframework/status/527119729631461376">Grails 2.4.4</a> released with bug fixes and improvements</li>
<li><a href="https://twitter.com/tomaslin/status/524798100041261058">Geb 0.10</a> released</li>
<li><a href="https://spring.io/blog/2014/10/21/reactor-2-0-0-m1-released-with-reactive-streams-integration">Reactor 2.0.0.M1</a> released</li>
<li><a href="https://twitter.com/cedricchampeau/status/526687506323238912">Groovy Android Gradle plugin 0.3.2</a> released fixing joint compilation issues</li>
<li><a href="https://twitter.com/cedricchampeau/status/527031406124609536">JMH Gradle plugin 0.1.3</a> released</li>
<li><a href="https://groups.google.com/forum/#!topic/gradle-dev/dpQkogqx_bw">Gretty 1.1.5</a> is out</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Roberto Guerra writes about <a href="http://blog.stumblingoncode.com/posts/2014-10-20-ratpack-groovy-chain.html">Ratpack handlers and Groovy chains</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Hans Dockter on <a href="https://thenewcircle.com/s/post/1673/gradle_and_the_road_ahead_hans_dockter_video">Gradle and the road ahead</a>, recorded at SF Java in June 2014</li>
<li>Cédric Champeau presented <a href="https://twitter.com/cedricchampeau/status/525608597863268353">Groovy on Android</a>, with a sample application using Google Wear, at SoftShake 2014</li>
<li>Danny Hyun <a href="https://twitter.com/Lspacewalker/status/524763500674310144">presented Ratpack</a> (slides and code included)</li>
<li>Guillaume Laforge gave his &ldquo;<a href="https://www.youtube.com/watch?v=8E3TdP_KIJo">What makes Groovy groovy</a>&rdquo; through Google Hangout at the Barcelona JUG</li>
<li>Iván López presented &ldquo;<a href="https://twitter.com/ilopmar/status/525549799638966273">Metaprogramming with Groovy</a>&rdquo; at GeeCON and offers links to his slides and code samples</li>
<li>More recordings of Trisha Gee&rsquo;s <a href="http://trishagee.github.io/presentation/angularjs_html5_groovy_java_mongodb_wcpgw/">AngularJS / HTML5 / Groovy / Java / MongoDB</a> talk</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Cédric Champeau recorded this video showing a <a href="https://vine.co/v/Ob2Z1dZrAFt">Google Wear application built with Groovy</a>, that he demonstrated at the Soft Shake 2014 conference</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Andrés Almiray repackages the huge <a href="https://github.com/aalmiray/google-materialdesignicons">Google Material Design icon as JARs that you can reuse</a> in your web / desktop / mobile projects</li>
<li>Jacob Aae Mikkelsen published the <a href="http://grydeske.net/news/show/67">Grails Diary week 43</a></li>
</ul>
<h2 id="google">Google+</h2>
<ul>
<li>A new <a href="https://plus.google.com/u/0/105332504458974739532/posts/L6edmMmQ6JR?cfem=1">CodinGame contest supporting Groovy</a> is happening soon</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Meet <a href="https://twitter.com/gradleware/status/524843728511320064">Gradle expert Peter Niederwieser in Stockholm</a></li>
<li>Use <a href="https://twitter.com/dailygrailstip/status/524964020478939136">Grails&rsquo; &ndash;non-interactive flag for easier Continuous Integration</a></li>
<li>A week into Groovy and Grails, and Yosi Pramajaya <a href="https://twitter.com/yosipramajaya/status/525469302594347008">celebrates Groovy and Grails&rsquo; awesomeness</a>!</li>
<li>Jorge Martín is looking into <a href="https://twitter.com/arasthel92/status/525914957599088640">adding some Groovy-ness to some Android APIs</a></li>
<li>Cédric Champeau wishes there were as many <a href="https://twitter.com/CedricChampeau/status/525010686951579648">contributions to the Groovy documentation</a> as there are edits on the Groovy entry on Wikipedia</li>
<li>Dierk König remarks a <a href="https://twitter.com/mittie/status/526137634671034368">programming language popularity tanking where Groovy ranks 8th</a></li>
<li><a href="https://twitter.com/Bl_nK_/status/525081570424532992">GPars makes concurrency on Groovy not suck</a></li>
<li>Burt Beckwith shares the link to a <a href="https://twitter.com/burtbeckwith/status/526440858217111553">tracker analyzing changes between Grails versions</a></li>
<li><a href="https://twitter.com/gvmtool/status/527126880714252288">Grails 2.4.4</a> available on GVM</li>
<li>Dierk König&rsquo;s been <a href="https://twitter.com/mittie/status/527115100654956544">programming in Groovy for 10 years</a> and is still amazed</li>
<li>After 10 years of Groovy, Dierk König notes that <a href="https://twitter.com/mittie/status/527129202588680193">Java finally has high-order functions</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau published the code of his <a href="https://twitter.com/cedricchampeau/status/525608882253877248">Google Wear Android application written in Groovy</a></li>
<li>Dierk König presents a <a href="https://gist.github.com/Dierk/358a2bf24cdcd10044e8">null-safe monad in Groovy</a> with Java 8 function</li>
<li>Stéphane Maldini shared a snippet showing a <a href="https://gist.github.com/smaldini/d6858b08773e028f5383">send &amp; receive RabbitMQ flow with Reactor&rsquo;s AMQP</a> support</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>MrHaki published the <a href="http://mrhaki.blogspot.fr/2014/10/spocklight-notebook-is-published.html">Spock Notebook</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/greachconf/status/525582247387480064">Greach conference is looking for sponsors</a>! If you or your company is willing to help, don&rsquo;t hesitate to get in touch with the Greach crew!</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #41</title><link>https://glaforge.dev/posts/2014/10/21/groovy-weekly-41/</link><pubDate>Tue, 21 Oct 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/10/21/groovy-weekly-41/</guid><description>&lt;p>Along with some valuable releases like the first release candidate of Gradle 2.2, there are interesting links on the topic of the Groovy Android story, as well as new glimpse on AST macros and AST pattern matching coming up. Worth a look!&lt;/p>
&lt;p>In terms of events, the Greach conference in Spain has opened its Call for Paper, and the GR8Conf Europe crew reminds us that all the great content from the last edition is freely available on their YouTube channel.&lt;/p></description><content:encoded>
<![CDATA[<p>Along with some valuable releases like the first release candidate of Gradle 2.2, there are interesting links on the topic of the Groovy Android story, as well as new glimpse on AST macros and AST pattern matching coming up. Worth a look!</p>
<p>In terms of events, the Greach conference in Spain has opened its Call for Paper, and the GR8Conf Europe crew reminds us that all the great content from the last edition is freely available on their YouTube channel.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/cedricchampeau/status/524123611766878209">Groovy Android Gradle plugin 0.3.1</a> released</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-2-rc-1-is-now-available-for-testing">Gradle 2.2.rc-1</a> is available</li>
<li><a href="http://groovy.329449.n5.nabble.com/ANN-Announcing-CodeNarc-0-22-td5721472.html">CodeNarc 0.22</a> released with new rules</li>
<li><a href="https://twitter.com/bsideup/status/522682180204376065">Groovy-macro-methods 0.2.0</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://kousenit.wordpress.com/2014/10/14/spaceships-elvis-and-groovy-inject/">Spaceships, Elvis, and Groovy inject</a> by Ken Kousen</li>
<li>An article about <a href="http://www.dexa-dev.com/groovy-saveinstance-on-swissknife/">@SaveInstance: a new AST transformation in SwissKnife</a>, the project that adds nice shortcuts for Android development with Groovy</li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/10/gradle-goodness-show-standard-out-or.html">show standard out or error output from tests</a></li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/10/gradle-goodness-changing-name-of.html">changing the name of the default build file</a></li>
<li><a href="http://www.eclecticlogic.com/2014/09/26/groovy-dsl-executor/">Groovier Groovy DSL</a> by Karthik Abram</li>
<li><a href="http://devops.judes.co.il/post/100396130279/artifactory-gradle-publishing-arbitrary-artifact">Publishing arbitrary artifacts with Gradle and Artifactory</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>The GR8Conf crew has published all the <a href="https://www.youtube.com/playlist?list=PLwxhnQ2Qv3xuE4JEKBpyE2AbbM_7G0EN1">videos of GR8Conf Europe 2014</a></li>
<li>Russel Winder presented <a href="https://speakerdeck.com/russelwinder/spocktacular-testing">Spock-tacular testing</a> at JAX London</li>
<li>Andrés Almiray and Ixchel Ruiz posted the slides of there JavaOne 2014 presentation titled <a href="http://fr.slideshare.net/aalmiray/javaone-gradle">Gradle: harder, better, stronger, faster</a></li>
<li><a href="http://fr.slideshare.net/gfrison/lean-reactiveserviceswithvertx">Reactive services on Vert.x with Groovy</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://grydeske.net/news/show/66">Grails Diary week 42</a> by Jacob Aae Mikkelsen</li>
<li>Caelyf, the lightweight toolkit for Cloud Foundry, is moving to a <a href="http://caelyf.net/">new domain</a></li>
</ul>
<h2 id="mailing-lists">Mailing-lists</h2>
<ul>
<li>Cédric Champeau gives an update on the ongoing work around Groovy AST macros, with some work around <a href="http://groovy.329449.n5.nabble.com/State-of-the-macros-td5721429.html#a5721475">AST pattern matching</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Kamil Szymański remarks that the release of <a href="https://twitter.com/kszdev/status/522662104344854529">JDK 8 update 25 solves the VerifyError affecting Groovy</a></li>
<li>Cédric Champeau shows a <a href="https://twitter.com/cedricchampeau/status/523934124898521090">Groovy demo application running on an Android smartwatch</a></li>
<li>Eugene Kamenev is working on <a href="https://twitter.com/eugenekamenev/status/523486523472109569">Grails-like domain classes for Groovy Android</a> development</li>
<li>Marcin Grzejszczak did an implementation of <a href="https://twitter.com/MGrzejszczak/status/523420864612925441">Consumer Driven Contracts in Groovy</a> at 4finance</li>
<li>The <a href="https://twitter.com/groovypuzzlers/status/524533757857267712">Groovy Puzzlers team wants to become at JavaOne rockstar</a> by collecting good survey results</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau is teasing his work around <a href="https://twitter.com/cedricchampeau/status/523116956275064833">AST macros and AST tree matching</a></li>
<li>Cédric Champeau shows another example of <a href="https://gist.github.com/melix/c02227081dd60f5cedaa">AST tree matching</a></li>
<li>Guillaume Laforge shows a quick example of using the newly released CoffeeScript javax.script engine to <a href="https://gist.github.com/glaforge/f7ddece9d4e0ff2afe82">execute some CoffeeScript code from Groovy</a></li>
<li>Another <a href="https://twitter.com/grooscript/status/523856523492335617">demo of GrooScript with the sigma.js</a> API</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The <a href="https://twitter.com/greachconf/status/524122916871368705">Call for Paper of the Greach</a> conference is open</li>
<li>Guillaume Laforge will speak at the <a href="https://twitter.com/BarcelonaJUG/status/523919077036015617">Barcelona JUG about What makes Groovy groovy</a> next Thursday</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #40</title><link>https://glaforge.dev/posts/2014/10/14/groovy-weekly-40/</link><pubDate>Tue, 14 Oct 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/10/14/groovy-weekly-40/</guid><description>&lt;p>No big highlight this week, but still plenty to learn from!&lt;/p>
&lt;p>We have some minor releases, interesting articles on Spock, on how to leverage traits with Geb, some thoughts on reactive programming, new demos and tutorials for GrooScript.&lt;/p>
&lt;p>And we can wish a happy birthday to the GR8 Ladies!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>Spring Tool Suite &amp;amp; &lt;a href="http://docs.spring.io/sts/nan/v362/NewAndNoteworthy.html">Groovy Grails Tool Suite 3.6.2&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/521314194319507458">GrooScript 0.6.1&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grainframework/status/520515098289651712">GridGain 0.6.2&lt;/a> with better support of themes that use plain CSS and better handling of large files&lt;/li>
&lt;li>&lt;a href="https://spring.io/blog/2014/10/11/spring-boot-1-1-8-released">Spring Boot 1.1.8&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://spring.io/blog/2014/10/11/spring-boot-1-2-0-m2-available-now">Spring Boot 1.2.0.M2&lt;/a> released&lt;/li>
&lt;li>&lt;a href="https://github.com/akhikhl/gretty/blob/master/changes.md">Gretty 1.1.4&lt;/a> released&lt;/li>
&lt;li>Alain Stalder released an &lt;a href="http://groovy.329449.n5.nabble.com/ANN-Grengine-User-Manual-td5721453.html">extensive manual for his Grengine&lt;/a>, his alternative approach to integrating Groovy in your applications&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>MrHaki&amp;rsquo;s Spocklight: &lt;a href="http://mrhaki.blogspot.fr/2014/10/spocklight-indicate-class-under-test.html">indicate class under test with @Subject annotation&lt;/a>&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Groovy Goodness: &lt;a href="http://mrhaki.blogspot.fr/2014/10/groovy-goodness-closure-as-class.html">Closure as a Class&lt;/a>&lt;/li>
&lt;li>Kyle Boon is leveraging &lt;a href="http://kyleboon.org/blog/2014/10/11/traitsandgeb/">Groovy traits with Geb&lt;/a>&lt;/li>
&lt;li>Luke Daley explains &lt;a href="http://ldaley.com/post/99527932537/executional-flexibility-through-reactive-programming">executional flexibility through reactive programming&lt;/a>&lt;/li>
&lt;li>A follow-up post from Andrés Almiray on the &lt;a href="http://www.jroller.com/aalmiray/entry/new_desktop_application_framework_jsr">new desktop application framework JSR&lt;/a>&lt;/li>
&lt;li>Savant, a &lt;a href="http://www.inversoft.com/blog/2014/10/08/introducing-savant-inversoft/">new Groovy-powered build tool&lt;/a>?&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://speakerdeck.com/jlstrater/2014-ghc-groovy-gr8ladies-workshop-v16">Groovy introduction&lt;/a> slides by the GR8 Ladies&lt;/li>
&lt;li>Colin Harrington on &lt;a href="http://slides.com/colinharrington/web-application-security#/">web security&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>Jacob Aae Mikkelsen&amp;rsquo;s &lt;a href="http://grydeske.net/news/show/65">Grails Diary&lt;/a> week 41&lt;/li>
&lt;/ul>
&lt;h2 id="interview">Interview&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/droidconuk/status/520861514396606464">Interview of Hans Dockter&lt;/a> on Gradle and continuous delivery&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/eugenekamenev/status/521007874529447937">Groovy makes Android code much more concise&lt;/a>&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/Gr8Ladies/status/520401087694917632">GR8 Ladies celebrate their first birthday&lt;/a>!&lt;/li>
&lt;li>Joe Wolf notes that &lt;a href="https://twitter.com/bdkosher/status/520295206219173888">Groovy reached 10,000 questions on Stack Overflow&lt;/a>&lt;/li>
&lt;li>Eugene Kamenev is investigating bring &lt;a href="https://twitter.com/eugenekamenev/status/521562112146276352">GORM-like features for Groovy on Android&lt;/a>&amp;lt;, to deal with building UI, handling validation, and more&lt;/li>
&lt;li>Youri Ackx liked Guillaume Laforge&amp;rsquo;s funny slide from his JavaOne presentation about how to represent &lt;a href="https://twitter.com/youriackx/status/519744695497273345">map / reduce, sandwich-style&lt;/a>&lt;/li>
&lt;li>Ratpack adds &lt;a href="https://twitter.com/ratpackweb/status/519830913106968577">more Promise operations&lt;/a> and improved the documentation&lt;/li>
&lt;li>Dierk König says the &lt;a href="https://twitter.com/mittie/status/520191158467960832">builder pattern in Groovy is a great example of the state monad&lt;/a>&lt;/li>
&lt;li>Graeme Rocher waves &lt;a href="https://twitter.com/graemerocher/status/520234393940787200">goodbye to the old Grails build system&lt;/a>&lt;/li>
&lt;li>Gradle features a handful of &lt;a href="https://twitter.com/DailyGrailsTip/status/520618252888584192">plugins for JavaScript / front-end build automation&lt;/a>&lt;/li>
&lt;li>One could wish all &lt;a href="https://twitter.com/groovylang/status/521424091447066624">front end development be done with Groovy and GrooScript&lt;/a>?&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/520927004351549440">Spring Boot 1.1.8&lt;/a> available through GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/520927209415249921">Spring Boot 1.2.0.M2&lt;/a> available through GVM&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/521760399398805504">GrooScript for client-side templating&lt;/a> with the HTML builder or with Groovy templates&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>Baruch Sadogursky will be speaking about AST transformations at &lt;a href="http://javaday.org.ua/#nav-speakers">JavaDay Kiev&lt;/a> and will wow the crowds with Groovy Puzzlers at JavaDay Kiev and &lt;a href="http://jokerconf.com/">Jokerconf&lt;/a>!&lt;/li>
&lt;li>Iván López will be talking about &lt;a href="http://geecon.cz/speakers/">&amp;ldquo;Metaprogramming with Groovy&amp;rdquo; at GeeCON Prague&lt;/a> next 23rd October&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/greachconf/status/521564797444190208">Greach conference&lt;/a> is being prepared and is taking shape, and the organizers are looking for sponsors&lt;/li>
&lt;li>&lt;a href="https://twitter.com/jacobaae/status/520910335570894848">GR8Conf Europe 2015&lt;/a> is in the works, as the team reunites&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>No big highlight this week, but still plenty to learn from!</p>
<p>We have some minor releases, interesting articles on Spock, on how to leverage traits with Geb, some thoughts on reactive programming, new demos and tutorials for GrooScript.</p>
<p>And we can wish a happy birthday to the GR8 Ladies!</p>
<h2 id="releases">Releases</h2>
<ul>
<li>Spring Tool Suite &amp; <a href="http://docs.spring.io/sts/nan/v362/NewAndNoteworthy.html">Groovy Grails Tool Suite 3.6.2</a> released</li>
<li><a href="https://twitter.com/grooscript/status/521314194319507458">GrooScript 0.6.1</a> released</li>
<li><a href="https://twitter.com/grainframework/status/520515098289651712">GridGain 0.6.2</a> with better support of themes that use plain CSS and better handling of large files</li>
<li><a href="https://spring.io/blog/2014/10/11/spring-boot-1-1-8-released">Spring Boot 1.1.8</a> released</li>
<li><a href="https://spring.io/blog/2014/10/11/spring-boot-1-2-0-m2-available-now">Spring Boot 1.2.0.M2</a> released</li>
<li><a href="https://github.com/akhikhl/gretty/blob/master/changes.md">Gretty 1.1.4</a> released</li>
<li>Alain Stalder released an <a href="http://groovy.329449.n5.nabble.com/ANN-Grengine-User-Manual-td5721453.html">extensive manual for his Grengine</a>, his alternative approach to integrating Groovy in your applications</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>MrHaki&rsquo;s Spocklight: <a href="http://mrhaki.blogspot.fr/2014/10/spocklight-indicate-class-under-test.html">indicate class under test with @Subject annotation</a></li>
<li>MrHaki&rsquo;s Groovy Goodness: <a href="http://mrhaki.blogspot.fr/2014/10/groovy-goodness-closure-as-class.html">Closure as a Class</a></li>
<li>Kyle Boon is leveraging <a href="http://kyleboon.org/blog/2014/10/11/traitsandgeb/">Groovy traits with Geb</a></li>
<li>Luke Daley explains <a href="http://ldaley.com/post/99527932537/executional-flexibility-through-reactive-programming">executional flexibility through reactive programming</a></li>
<li>A follow-up post from Andrés Almiray on the <a href="http://www.jroller.com/aalmiray/entry/new_desktop_application_framework_jsr">new desktop application framework JSR</a></li>
<li>Savant, a <a href="http://www.inversoft.com/blog/2014/10/08/introducing-savant-inversoft/">new Groovy-powered build tool</a>?</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="https://speakerdeck.com/jlstrater/2014-ghc-groovy-gr8ladies-workshop-v16">Groovy introduction</a> slides by the GR8 Ladies</li>
<li>Colin Harrington on <a href="http://slides.com/colinharrington/web-application-security#/">web security</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen&rsquo;s <a href="http://grydeske.net/news/show/65">Grails Diary</a> week 41</li>
</ul>
<h2 id="interview">Interview</h2>
<ul>
<li><a href="https://twitter.com/droidconuk/status/520861514396606464">Interview of Hans Dockter</a> on Gradle and continuous delivery</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/eugenekamenev/status/521007874529447937">Groovy makes Android code much more concise</a></li>
<li>The <a href="https://twitter.com/Gr8Ladies/status/520401087694917632">GR8 Ladies celebrate their first birthday</a>!</li>
<li>Joe Wolf notes that <a href="https://twitter.com/bdkosher/status/520295206219173888">Groovy reached 10,000 questions on Stack Overflow</a></li>
<li>Eugene Kamenev is investigating bring <a href="https://twitter.com/eugenekamenev/status/521562112146276352">GORM-like features for Groovy on Android</a>&lt;, to deal with building UI, handling validation, and more</li>
<li>Youri Ackx liked Guillaume Laforge&rsquo;s funny slide from his JavaOne presentation about how to represent <a href="https://twitter.com/youriackx/status/519744695497273345">map / reduce, sandwich-style</a></li>
<li>Ratpack adds <a href="https://twitter.com/ratpackweb/status/519830913106968577">more Promise operations</a> and improved the documentation</li>
<li>Dierk König says the <a href="https://twitter.com/mittie/status/520191158467960832">builder pattern in Groovy is a great example of the state monad</a></li>
<li>Graeme Rocher waves <a href="https://twitter.com/graemerocher/status/520234393940787200">goodbye to the old Grails build system</a></li>
<li>Gradle features a handful of <a href="https://twitter.com/DailyGrailsTip/status/520618252888584192">plugins for JavaScript / front-end build automation</a></li>
<li>One could wish all <a href="https://twitter.com/groovylang/status/521424091447066624">front end development be done with Groovy and GrooScript</a>?</li>
<li><a href="https://twitter.com/gvmtool/status/520927004351549440">Spring Boot 1.1.8</a> available through GVM</li>
<li><a href="https://twitter.com/gvmtool/status/520927209415249921">Spring Boot 1.2.0.M2</a> available through GVM</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/521760399398805504">GrooScript for client-side templating</a> with the HTML builder or with Groovy templates</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Baruch Sadogursky will be speaking about AST transformations at <a href="http://javaday.org.ua/#nav-speakers">JavaDay Kiev</a> and will wow the crowds with Groovy Puzzlers at JavaDay Kiev and <a href="http://jokerconf.com/">Jokerconf</a>!</li>
<li>Iván López will be talking about <a href="http://geecon.cz/speakers/">&ldquo;Metaprogramming with Groovy&rdquo; at GeeCON Prague</a> next 23rd October</li>
<li>The <a href="https://twitter.com/greachconf/status/521564797444190208">Greach conference</a> is being prepared and is taking shape, and the organizers are looking for sponsors</li>
<li><a href="https://twitter.com/jacobaae/status/520910335570894848">GR8Conf Europe 2015</a> is in the works, as the team reunites</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Back from JavaOne</title><link>https://glaforge.dev/talks/2014/10/07/back-from-javaone/</link><pubDate>Tue, 07 Oct 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2014/10/07/back-from-javaone/</guid><description>&lt;p>After some trouble getting to San Francisco because of strikes, transportation issues, burnt control tower, and more&amp;hellip; I managed to land to JavaOne! I missed my own first talk and I was glad and grateful that Cédric could present it for me.&lt;/p>
&lt;p>At JavaOne, the Groovy project received the award from ZeroTurnaround / RebelLabs, for the &amp;ldquo;Best Tech - Geek Choice Award&amp;rdquo; :&lt;/p>
&lt;blockquote>
&lt;p>Youpi mon projet a reçu un prix de l&amp;rsquo;innovation ! Très fier de ce que mon équipe a accompli !&lt;/p></description><content:encoded>
<![CDATA[<p>After some trouble getting to San Francisco because of strikes, transportation issues, burnt control tower, and more&hellip; I managed to land to JavaOne! I missed my own first talk and I was glad and grateful that Cédric could present it for me.</p>
<p>At JavaOne, the Groovy project received the award from ZeroTurnaround / RebelLabs, for the &ldquo;Best Tech - Geek Choice Award&rdquo; :</p>
<blockquote>
<p>Youpi mon projet a reçu un prix de l&rsquo;innovation ! Très fier de ce que mon équipe a accompli !</p>
<p><a href="https://instagram.com/p/tuU20HgNf7/">Instagram</a></p></blockquote>
<p>And at the famous &ldquo;Script Bowl&rdquo; competition, I managed to get on the first place of the podium, along with the Clojure representative, showing Groovy traits, Groovy on Android, and GrooScript transpiling Groovy into JavaScript.</p>
<blockquote>
<p><a href="https://twitter.com/hashtag/groovylang?src=hash">#groovylang</a> still first on the podium of the <a href="https://twitter.com/hashtag/JavaOne?src=hash">#JavaOne</a> script bowl, along with <a href="https://twitter.com/hashtag/clojure?src=hash">#clojure</a> too :-) Thanks to all the attendees!</p>
<p>— Guillaume Laforge (@glaforge) <a href="https://twitter.com/glaforge/status/517485161496051712">October 2, 2014</a></p></blockquote>
<p>And last but not least, my presentation slides&hellip;</p>
<script async class="speakerdeck-embed" data-id="69e2f5e02c6a013279dd1ea472fb96ba" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<script async class="speakerdeck-embed" data-id="937906e02c6a0132389e4274f6195918" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy on Android at DroidCon Paris 2014</title><link>https://glaforge.dev/talks/2014/10/07/groovy-on-android-at-droidcon-paris-2014/</link><pubDate>Tue, 07 Oct 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/talks/2014/10/07/groovy-on-android-at-droidcon-paris-2014/</guid><description>&lt;p>I was very honored to be able to speak about Groovy at DroidCon Paris 2014 a couple of weeks ago. It was really great to spend time with the Android community and learn more about their needs, pain points, and more.&lt;/p>
&lt;p>The video of the presentation is available below:&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/kOVxiob9ZgQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;p>And you can find the slides here too:&lt;/p></description><content:encoded>
<![CDATA[<p>I was very honored to be able to speak about Groovy at DroidCon Paris 2014 a couple of weeks ago. It was really great to spend time with the Android community and learn more about their needs, pain points, and more.</p>
<p>The video of the presentation is available below:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/kOVxiob9ZgQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>And you can find the slides here too:</p>
<script async class="speakerdeck-embed" data-id="4a2f75502495013299fd7e85eb08a929" data-ratio="1.77777777" src="//speakerdeck.com/assets/embed.js"></script>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #39</title><link>https://glaforge.dev/posts/2014/10/07/groovy-weekly-39/</link><pubDate>Tue, 07 Oct 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/10/07/groovy-weekly-39/</guid><description>&lt;p>Back from JavaOne, where the Groovy project received its &amp;ldquo;Best Tech - Geek Choice Awards&amp;rdquo; from ZeroTurnaround, and where it win the Script Bowl competion this year again!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://new.griffon-framework.org/news/griffon_2.0.0.html">Griffon 2.0.0&lt;/a> final released!&lt;/li>
&lt;li>&lt;a href="http://www.ratpack.io/versions/0.9.9">Ratpack 0.9.9&lt;/a> is out moving to Java 8 as a base requirement&lt;/li>
&lt;li>Alain Stalder releases &lt;a href="http://groovy.329449.n5.nabble.com/ANN-Grengine-td5721425.html">Grengine&lt;/a>, another approach to running and embedding Groovy&lt;/li>
&lt;li>&lt;a href="https://twitter.com/RxJava/status/517187762307350528">RxGroovy 1.0.0-RC-1&lt;/a> was released with a new artifact ID&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Marcin Zajączkowski comes back with Part 2 of &lt;a href="http://solidsoft.wordpress.com/2014/10/03/whats-new-in-upcoming-spock-1-0-part-2-cleaning-up/">what&amp;rsquo;s coming up in Spock 1.0&lt;/a>&lt;/li>
&lt;li>Andrés Almiray explains it&amp;rsquo;s &lt;a href="http://www.jroller.com/aalmiray/entry/it_s_time_for_a">time for a desktop application framework JSR&lt;/a>&lt;/li>
&lt;li>Trisha Gee is using &lt;a href="http://trishagee.github.io/post/groovy_import_to_mongodb/">Groovy to insert XML into MongoDB&lt;/a>&lt;/li>
&lt;li>Hans Dockter published the &lt;a href="http://www.gradleware.com/newsletter/gradleware-newsletter-july-aug-sep/">Gradleware July-September newsletter&lt;/a>&lt;/li>
&lt;li>André Steingreß writes about &lt;a href="http://blog.andresteingress.com/2014/10/04/grails-principal-stamp/">principals tracking in Grails&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://rpeszek.blogspot.fr/2014/10/i-dont-like-hibernategrails-part-8-but.html">Part 8&lt;/a> of the series “I don’t like Hibernate / Grails”&lt;/li>
&lt;li>A &lt;a href="http://blog.andresteingress.com/2014/10/05/grails-integration-tests/">Spock quick tip for Grails integration tests&lt;/a> from André Steingreß&lt;/li>
&lt;li>Quick Gradle tip: &lt;a href="https://andrew-oberstar.squarespace.com/blog/2014/9/28/quick-gradle-tip-setting-bintray-attributes-for-gradle-plugins">setting Bintray attributes for Gradle plugins&lt;/a>&lt;/li>
&lt;li>Tomás Lin on &lt;a href="http://fbflex.wordpress.com/2014/09/22/spring-boot-and-webjars-overwriting-configuration-during-runtime/">Spring Boot and Webjars&lt;/a>: overwriting configuration during runtime&lt;/li>
&lt;li>Burt Beckwith answers a Stack Overflow question on &lt;a href="http://stackoverflow.com/questions/26127364/override-default-grails-create-app-files/26131261#26131261">overriding Grails&amp;rsquo; default create-app files&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>Ixchel Ruiz and Andrés Almiray demonstrated the &lt;a href="http://fr.slideshare.net/aalmiray/javaone-getting-funky-with-groovy">funky-ness of Groovy&lt;/a> at JavaOne 2014&lt;/li>
&lt;li>Cédric Champeau presented “&lt;a href="https://speakerdeck.com/melix/rethinking-api-design-with-traits-in-groovy">rethinking API design with traits in Groovy&lt;/a>” at JavaOne 2014&lt;/li>
&lt;li>Guillaume Laforge posted his JavaOne 2014 slides about “&lt;a href="https://speakerdeck.com/glaforge/groovy-in-the-light-of-java-8-javaone-2014">Groovy in the light of Java 8&lt;/a>”&lt;/li>
&lt;li>Guillaume Laforge published his slides about “&lt;a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-javaone-2014">Groovy in 2014 and beyond&lt;/a>” from JavaOne 2014&lt;/li>
&lt;li>Guillaume Laforge presented &lt;a href="https://www.youtube.com/watch?v=kOVxiob9ZgQ">Groovy on Android&lt;/a> at DroidCon Paris 2014&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>Jacob Aae Mikkelsen’s &lt;a href="http://grydeske.net/news/show/63">Grails Diary week 39&lt;/a>&lt;/li>
&lt;li>Jacob Aae Mikkelsen’s &lt;a href="http://grydeske.net/news/show/64">Grails Diary week 40&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/glaforge/status/517485161496051712">Groovy reaches first place on the podium&lt;/a>, along with Clojure, beating JRuby and Scala at JavaOne 2014&amp;rsquo;s Script Bowl competition&lt;/li>
&lt;li>&lt;a href="https://twitter.com/glaforge/status/518307473766514688">Groovy received its Best Tech Geek Choice Award&lt;/a> from ZeroTurnaround at JavaOne 2014&lt;/li>
&lt;li>Cédric Champeau is looking into &lt;a href="https://twitter.com/cedricchampeau/status/519102857812144128">integrating Sergei Egorov’s Groovy macros&lt;/a> into Groovy 2.4&lt;/li>
&lt;li>Dierk König says the &lt;a href="https://twitter.com/mittie/status/517456134018318336">finish line is in sight for Groovy in Action&lt;/a>, second edition&lt;/li>
&lt;li>If you use dependency classifiers (like Groovy’s “indy”) in Gradle builds with IntelliJ IDEA, you might want to &lt;a href="https://twitter.com/SolidSoftBlog/status/515043592511844353">vote for this issue as it’s broken in Intellij IDEA&lt;/a>&lt;/li>
&lt;li>If you like &lt;a href="https://twitter.com/Lspacewalker/status/516711346801373184">SASS and Gradle&lt;/a>, Danny Hyun notes there&amp;rsquo;s a plugin for that&lt;/li>
&lt;li>Russel Hart notes that as of Ratpack 0.9.9 you can &lt;a href="https://twitter.com/rus_hart/status/517275624197865472">stream your Hystrix metrics out to the Hystrix dashboard or Turbine&lt;/a>&lt;/li>
&lt;li>Russel Hart remarks that &lt;a href="https://twitter.com/rus_hart/status/517273884845170688">Ratpack&amp;rsquo;s GroovyChainAction is replaced&lt;/a> with Action&amp;lt;Chain&amp;gt; and the Groovy.chain() call in Ratpack 0.9.9&lt;/li>
&lt;li>Craig Burke is readying his &lt;a href="https://twitter.com/craigburke1/status/519201978694778880">Angular.JS Grails Lazybones template&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Ken Sipe’s code samples from his JavaOne 2014 &lt;a href="https://github.com/kensipe/spock-javaone2014">Spock presentation&lt;/a>&lt;/li>
&lt;li>More &lt;a href="https://twitter.com/grooscript/status/517449135897993216">GrooScript demos&lt;/a> available&lt;/li>
&lt;li>&lt;a href="https://github.com/ratpack/ratpack/commit/cc41092f7cc6?diff=split">Ratpack promises, a comparison between Java 7 and 8&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>Mac Liaw will be speaking about &lt;a href="http://1devday.net/sessions/ratpack-fun-profit/">Ratpack at 1 Dev Day Detroit&lt;/a>&lt;/li>
&lt;li>Andrés Almiray is presenting &lt;a href="http://jazoon.com/talk/Talk122/">Gradle, harder, better, stronger, faster&lt;/a>, at Jazoon&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>Jon Kerridge publishes &lt;a href="http://gpars-user-mailing-list.19372.n3.nabble.com/Using-Concurrency-and-Parallelism-Effectively-td4025035.html">two ebooks on concurrency and parallelism&lt;/a> and makes good use of Groovy&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Back from JavaOne, where the Groovy project received its &ldquo;Best Tech - Geek Choice Awards&rdquo; from ZeroTurnaround, and where it win the Script Bowl competion this year again!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://new.griffon-framework.org/news/griffon_2.0.0.html">Griffon 2.0.0</a> final released!</li>
<li><a href="http://www.ratpack.io/versions/0.9.9">Ratpack 0.9.9</a> is out moving to Java 8 as a base requirement</li>
<li>Alain Stalder releases <a href="http://groovy.329449.n5.nabble.com/ANN-Grengine-td5721425.html">Grengine</a>, another approach to running and embedding Groovy</li>
<li><a href="https://twitter.com/RxJava/status/517187762307350528">RxGroovy 1.0.0-RC-1</a> was released with a new artifact ID</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Marcin Zajączkowski comes back with Part 2 of <a href="http://solidsoft.wordpress.com/2014/10/03/whats-new-in-upcoming-spock-1-0-part-2-cleaning-up/">what&rsquo;s coming up in Spock 1.0</a></li>
<li>Andrés Almiray explains it&rsquo;s <a href="http://www.jroller.com/aalmiray/entry/it_s_time_for_a">time for a desktop application framework JSR</a></li>
<li>Trisha Gee is using <a href="http://trishagee.github.io/post/groovy_import_to_mongodb/">Groovy to insert XML into MongoDB</a></li>
<li>Hans Dockter published the <a href="http://www.gradleware.com/newsletter/gradleware-newsletter-july-aug-sep/">Gradleware July-September newsletter</a></li>
<li>André Steingreß writes about <a href="http://blog.andresteingress.com/2014/10/04/grails-principal-stamp/">principals tracking in Grails</a></li>
<li><a href="http://rpeszek.blogspot.fr/2014/10/i-dont-like-hibernategrails-part-8-but.html">Part 8</a> of the series “I don’t like Hibernate / Grails”</li>
<li>A <a href="http://blog.andresteingress.com/2014/10/05/grails-integration-tests/">Spock quick tip for Grails integration tests</a> from André Steingreß</li>
<li>Quick Gradle tip: <a href="https://andrew-oberstar.squarespace.com/blog/2014/9/28/quick-gradle-tip-setting-bintray-attributes-for-gradle-plugins">setting Bintray attributes for Gradle plugins</a></li>
<li>Tomás Lin on <a href="http://fbflex.wordpress.com/2014/09/22/spring-boot-and-webjars-overwriting-configuration-during-runtime/">Spring Boot and Webjars</a>: overwriting configuration during runtime</li>
<li>Burt Beckwith answers a Stack Overflow question on <a href="http://stackoverflow.com/questions/26127364/override-default-grails-create-app-files/26131261#26131261">overriding Grails&rsquo; default create-app files</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Ixchel Ruiz and Andrés Almiray demonstrated the <a href="http://fr.slideshare.net/aalmiray/javaone-getting-funky-with-groovy">funky-ness of Groovy</a> at JavaOne 2014</li>
<li>Cédric Champeau presented “<a href="https://speakerdeck.com/melix/rethinking-api-design-with-traits-in-groovy">rethinking API design with traits in Groovy</a>” at JavaOne 2014</li>
<li>Guillaume Laforge posted his JavaOne 2014 slides about “<a href="https://speakerdeck.com/glaforge/groovy-in-the-light-of-java-8-javaone-2014">Groovy in the light of Java 8</a>”</li>
<li>Guillaume Laforge published his slides about “<a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-javaone-2014">Groovy in 2014 and beyond</a>” from JavaOne 2014</li>
<li>Guillaume Laforge presented <a href="https://www.youtube.com/watch?v=kOVxiob9ZgQ">Groovy on Android</a> at DroidCon Paris 2014</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/63">Grails Diary week 39</a></li>
<li>Jacob Aae Mikkelsen’s <a href="http://grydeske.net/news/show/64">Grails Diary week 40</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/glaforge/status/517485161496051712">Groovy reaches first place on the podium</a>, along with Clojure, beating JRuby and Scala at JavaOne 2014&rsquo;s Script Bowl competition</li>
<li><a href="https://twitter.com/glaforge/status/518307473766514688">Groovy received its Best Tech Geek Choice Award</a> from ZeroTurnaround at JavaOne 2014</li>
<li>Cédric Champeau is looking into <a href="https://twitter.com/cedricchampeau/status/519102857812144128">integrating Sergei Egorov’s Groovy macros</a> into Groovy 2.4</li>
<li>Dierk König says the <a href="https://twitter.com/mittie/status/517456134018318336">finish line is in sight for Groovy in Action</a>, second edition</li>
<li>If you use dependency classifiers (like Groovy’s “indy”) in Gradle builds with IntelliJ IDEA, you might want to <a href="https://twitter.com/SolidSoftBlog/status/515043592511844353">vote for this issue as it’s broken in Intellij IDEA</a></li>
<li>If you like <a href="https://twitter.com/Lspacewalker/status/516711346801373184">SASS and Gradle</a>, Danny Hyun notes there&rsquo;s a plugin for that</li>
<li>Russel Hart notes that as of Ratpack 0.9.9 you can <a href="https://twitter.com/rus_hart/status/517275624197865472">stream your Hystrix metrics out to the Hystrix dashboard or Turbine</a></li>
<li>Russel Hart remarks that <a href="https://twitter.com/rus_hart/status/517273884845170688">Ratpack&rsquo;s GroovyChainAction is replaced</a> with Action&lt;Chain&gt; and the Groovy.chain() call in Ratpack 0.9.9</li>
<li>Craig Burke is readying his <a href="https://twitter.com/craigburke1/status/519201978694778880">Angular.JS Grails Lazybones template</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Ken Sipe’s code samples from his JavaOne 2014 <a href="https://github.com/kensipe/spock-javaone2014">Spock presentation</a></li>
<li>More <a href="https://twitter.com/grooscript/status/517449135897993216">GrooScript demos</a> available</li>
<li><a href="https://github.com/ratpack/ratpack/commit/cc41092f7cc6?diff=split">Ratpack promises, a comparison between Java 7 and 8</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Mac Liaw will be speaking about <a href="http://1devday.net/sessions/ratpack-fun-profit/">Ratpack at 1 Dev Day Detroit</a></li>
<li>Andrés Almiray is presenting <a href="http://jazoon.com/talk/Talk122/">Gradle, harder, better, stronger, faster</a>, at Jazoon</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Jon Kerridge publishes <a href="http://gpars-user-mailing-list.19372.n3.nabble.com/Using-Concurrency-and-Parallelism-Effectively-td4025035.html">two ebooks on concurrency and parallelism</a> and makes good use of Groovy</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #38</title><link>https://glaforge.dev/posts/2014/09/29/groovy-weekly-38/</link><pubDate>Mon, 29 Sep 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/09/29/groovy-weekly-38/</guid><description>&lt;p>Here’s a short column this week, a bit in advance, as I’m flying to JavaOne tomorrow to spread the Groovy truth!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau announced the release of &lt;a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-2-3-5-released-and-upward-compatibility-td5720556.html#a5721353">Groovy 2.3.7&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://spring.io/blog/2014/09/26/spring-boot-1-1-7-released">Spring Boot 1.1.7&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>MrHaki&amp;rsquo;s Gradle Goodness: &lt;a href="http://mrhaki.blogspot.fr/2014/09/gradle-goodness-running-groovy-scripts.html">running Groovy scripts as application&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://kousenit.wordpress.com/2014/09/17/the-reason-the-internet-was-invented-or-cat-pictures-ftw/">The reason the internet was invented or cat pictures for the win&lt;/a>, by Ken Kousen, sprinkled with bits of Groovy JSON and Swing code&lt;/li>
&lt;li>&lt;a href="http://razum.si/blog/grails-javascript-i18n-messages">Exporting Grails&amp;rsquo; i18n messages into Javascript&lt;/a>&lt;/li>
&lt;li>Andrés Almiray’s &lt;a href="http://www.jroller.com/aalmiray/entry/gradle_glam_future_features">Gradle Glam on features that would benefit the Gradle community&lt;/a>&lt;/li>
&lt;li>Jorge Martin on the &lt;a href="http://blog.arasthel.com/so-i-made-this-thing-english/">inception and the Groovy Swiss Knife library for Android and the problems encountered with Android Studio&amp;rsquo;s Groovy support&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>Guillaume Laforge presented on &lt;a href="https://speakerdeck.com/glaforge/groovy-on-android-droidcon-paris-2014">Groovy on Android&lt;/a> at DroidCon Paris 2014&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Robert Fletcher noticed &lt;a href="https://twitter.com/rfletcherew/status/516585075077832705">IntelliJ IDEA&amp;rsquo;s clever capability of properly renaming closures it parameter&lt;/a>&lt;/li>
&lt;li>Java&amp;rsquo;s &lt;a href="https://twitter.com/aalmiray/status/516400403487809536">Duke is looking pretty Groovy&lt;/a> at JavaOne!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/515216180333002752">Groovy 2.3.7&lt;/a> is available on GVM&lt;/li>
&lt;li>A &lt;a href="https://twitter.com/gregturn/status/514759918633881600">pretty Groovy book&lt;/a>!&lt;/li>
&lt;li>Jon DeJong &lt;a href="https://twitter.com/jondejong/status/515233596731559936">makes Groovy strongly typed by hitting the keys harder&lt;/a> when coding!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/Gradleware/status/515021179640246272">New component selection and replacement rules&lt;/a> for upcoming Gradle 2.2&lt;/li>
&lt;li>Robert Guerra shows a screenshot of &lt;a href="https://twitter.com/robertoguerra19/status/514990054880866304">deployment of Ratpack with Java 8 on Heroku&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau shows a fun snippet of code using &lt;a href="https://gist.github.com/melix/5bc7c5db7ab986924181">metaprogramming to let Groovy be pretty tolerant to typos in method names&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Here’s a short column this week, a bit in advance, as I’m flying to JavaOne tomorrow to spread the Groovy truth!</p>
<h2 id="releases">Releases</h2>
<ul>
<li>Cédric Champeau announced the release of <a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-2-3-5-released-and-upward-compatibility-td5720556.html#a5721353">Groovy 2.3.7</a></li>
<li><a href="https://spring.io/blog/2014/09/26/spring-boot-1-1-7-released">Spring Boot 1.1.7</a> released</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/09/gradle-goodness-running-groovy-scripts.html">running Groovy scripts as application</a></li>
<li><a href="http://kousenit.wordpress.com/2014/09/17/the-reason-the-internet-was-invented-or-cat-pictures-ftw/">The reason the internet was invented or cat pictures for the win</a>, by Ken Kousen, sprinkled with bits of Groovy JSON and Swing code</li>
<li><a href="http://razum.si/blog/grails-javascript-i18n-messages">Exporting Grails&rsquo; i18n messages into Javascript</a></li>
<li>Andrés Almiray’s <a href="http://www.jroller.com/aalmiray/entry/gradle_glam_future_features">Gradle Glam on features that would benefit the Gradle community</a></li>
<li>Jorge Martin on the <a href="http://blog.arasthel.com/so-i-made-this-thing-english/">inception and the Groovy Swiss Knife library for Android and the problems encountered with Android Studio&rsquo;s Groovy support</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Guillaume Laforge presented on <a href="https://speakerdeck.com/glaforge/groovy-on-android-droidcon-paris-2014">Groovy on Android</a> at DroidCon Paris 2014</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Robert Fletcher noticed <a href="https://twitter.com/rfletcherew/status/516585075077832705">IntelliJ IDEA&rsquo;s clever capability of properly renaming closures it parameter</a></li>
<li>Java&rsquo;s <a href="https://twitter.com/aalmiray/status/516400403487809536">Duke is looking pretty Groovy</a> at JavaOne!</li>
<li><a href="https://twitter.com/gvmtool/status/515216180333002752">Groovy 2.3.7</a> is available on GVM</li>
<li>A <a href="https://twitter.com/gregturn/status/514759918633881600">pretty Groovy book</a>!</li>
<li>Jon DeJong <a href="https://twitter.com/jondejong/status/515233596731559936">makes Groovy strongly typed by hitting the keys harder</a> when coding!</li>
<li><a href="https://twitter.com/Gradleware/status/515021179640246272">New component selection and replacement rules</a> for upcoming Gradle 2.2</li>
<li>Robert Guerra shows a screenshot of <a href="https://twitter.com/robertoguerra19/status/514990054880866304">deployment of Ratpack with Java 8 on Heroku</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Cédric Champeau shows a fun snippet of code using <a href="https://gist.github.com/melix/5bc7c5db7ab986924181">metaprogramming to let Groovy be pretty tolerant to typos in method names</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #37</title><link>https://glaforge.dev/posts/2014/09/23/groovy-weekly-37/</link><pubDate>Tue, 23 Sep 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/09/23/groovy-weekly-37/</guid><description>&lt;p>SpringOne2GX 2014 in Dallas is over. And next week starts JavaOne. It’s conference season! Speaking of which… the fine GR8Conf US crew released many more presentations this week! There’s a lot of content to go through!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/512724108941881344">GrooScript 0.6&lt;/a> is out, on the road to 1.0, and Jorge Franco is looking for your feedback&lt;/li>
&lt;li>&lt;a href="https://twitter.com/droidxav/status/512731018097209344">Gradle Android plugin 0.13&lt;/a> works with Gradle 2.1&lt;/li>
&lt;li>&lt;a href="https://twitter.com/javabake/status/513793973857828864">JBake 2.3.2&lt;/a> with Asciidoctor 1.5&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://www.gradle.org/docs/2.1/release-notes#incremental-java-compilation">Incremental Java compilation landed in Gradle&lt;/a> 2.1&lt;/li>
&lt;li>&lt;a href="http://grydeske.net/news/show/62">Grails Diary week 38&lt;/a> by Jacob Aae Mikkelsen&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://solidsoft.wordpress.com/2014/09/17/whats-new-in-upcoming-spock-1-0-simpler-conditional-test-execution-with-requires-and-ignoreif/">What&amp;rsquo;s new in Spock 1.0&lt;/a>, by Marcin Zajączkowski&lt;/li>
&lt;li>&lt;a href="http://mariogarcia.github.io/gpars-workshop/#_remoting">GPars remote dataflows and actors&lt;/a> by Mario García&lt;/li>
&lt;li>&lt;a href="http://grooscript.org/react_example.html">Writing React.JS components in Groovy with GrooScript&lt;/a>&lt;/li>
&lt;li>The &amp;ldquo;&lt;a href="http://grailsrevolution.wordpress.com/2014/09/18/grails-revolution-the-force/">Grails revolution&lt;/a>&amp;rdquo; movement is looking forward to spreading the word on how great Grails is&lt;/li>
&lt;li>What&amp;rsquo;s new with &lt;a href="http://devsoap.com/#!/What-is-new-with-Gradle-Vaadin-Plugin-09">Gradle Vaadin Plugin 0.9&lt;/a>? Groovy support!&lt;/li>
&lt;li>MrHaki&amp;rsquo;s Gradle Goodness: &lt;a href="http://mrhaki.blogspot.fr/2014/09/gradle-goodness-adding-dependencies.html">Adding dependencies only for packaging to war&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://csiebler.github.io/blog/2014/02/09/multi-project-code-coverage-using-gradle-and-jacoco/">Multi-project code coverage using Gradle and JaCoCo&lt;/a> by Clemens Siebler&lt;/li>
&lt;li>After his Hibernate/Grails series, Robert Peszek comes to functional programming: &amp;ldquo;My dream: software without any bugs &amp;hellip; and &lt;a href="http://rpeszek.blogspot.fr/2014/09/my-dream-software-without-any-bugs-and.html">is Groovy functional? How about Grails&lt;/a>?&amp;rdquo;&lt;/li>
&lt;li>How to &lt;a href="https://www.timroes.de/2013/09/12/speed-up-gradle/">speed up Gradle builds? Use the daemon&lt;/a>!&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>Guillaume Laforge presented about &lt;a href="https://speakerdeck.com/glaforge/groovy-on-android-droidcon-paris-2014">Groovy&amp;rsquo;s Android support&lt;/a> at DroidCon Paris 2014&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=RuTupC0I59M&amp;amp;feature=youtu.be">Idiomatic Spock&lt;/a> with Robert Fletcher recorded at Netflix&lt;/li>
&lt;li>Trisha Gee gave her &lt;a href="http://virtualjug.com/html5-angularjs-groovy-java-and-mongodb-all-together-what-could-go-wrong/">Groovy / Angular.JS / HTML 5 / MongoDB&lt;/a> talk to the Virtual JUG&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=f_fGwa2-rMk">Micro-services with Groovy and Spring Boot&lt;/a> by Marcin Grzejszczak&lt;/li>
&lt;li>GR8Conf US 2014 presentations
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=0aYcLMJkv5c">Groovy at SmartThings&lt;/a> presented by Scott Vlaminck and Ryan Applegate&lt;/li>
&lt;li>The &lt;a href="https://www.youtube.com/watch?v=O0vt57XeF5E&amp;amp;feature=youtu.be">Groovy Ecosystem revisited&lt;/a> by Andrés Almiray&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=g0fX5yA-Xzg&amp;amp;feature=youtu.be">Getting started with Ratpack&lt;/a> presented by Jeff Beck&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=Ap16ueXSJig">Realtime Ratpack&lt;/a> by Robert Fletcher&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=G4EAd514jdE">Introduction to Gradle&lt;/a> by John Engelman&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=qyj7A-8n49w">Grails Goodness&lt;/a> presented by Hubert Klein Ikkink&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=erg5Ng8yuKQ">Gradle Goodness&lt;/a> presented by Hubert Klein Ikkink&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=u3KpiLZ1-64&amp;amp;feature=youtu.be">Distributed platform development with Groovy&lt;/a> by Robert Fletcher&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=unUJwAAtLcM">Introduction to multithreaded programing with GPars&lt;/a> by Jon DeJong&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=ZYISj1RmQA0&amp;amp;feature=youtu.be">Tour de plugin&lt;/a>, by Søren Berg Glasius&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=tSbx2MSsD5A">Grails Worst Practices&lt;/a> by Burt Beckwith&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=QhVCYLgHak4&amp;amp;feature=youtu.be">Griffon, what&amp;rsquo;s new and what&amp;rsquo;s coming&lt;/a>, by Andrés Almiray&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=u8Ib2JFcyX8">How to test your Grails application&lt;/a> presented by Colin Harrington&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=UlJByqywIkE&amp;amp;feature=youtu.be">Hybrid view rendering with Grails&lt;/a> by Robert Fletcher&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=lL_wQF5AjH8">Testing BitCoin and MasterCoin with Spock&lt;/a> by Sean Gilligan&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=EnyKt7NQTEY">Performance tuning Grails applications&lt;/a> by Lari Hotari&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=1umis5WS8_w">What&amp;rsquo;s new in the Grails Spring security plugin&lt;/a> by Burt Beckwith&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=Rid_hOnJTrk">Application architectures in Grails&lt;/a> by Peter Ledbrook&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=dV-a21JQ21k">Idiomatic Spock&lt;/a> by Robert Fletcher&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=hHMaTZarRjY">Getting Grails into the Enterprise&lt;/a> by Ryan Vanderwerf&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=YG1hWfLDuBo">Metaprogramming with the Groovy Runtime&lt;/a> by Jeff Brown&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=OFFjqeqt8Ds">Getting Groovy with Graphs&lt;/a> by Stefan Armbruster&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=LyJ4fORkHfA">Serving Websockets to iOS, Android and the Web using Groovy, Spring, Angular.js, SockJs and STOMP&lt;/a> presented by Sean Gilligan&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="screencasts">Screencasts&lt;/h2>
&lt;ul>
&lt;li>Bertrand Goetzmann publishes a screen cast of &lt;a href="https://www.youtube.com/watch?v=QHGqxMpxqMU&amp;amp;feature=youtu.be">Grails with the Ionic framework&lt;/a> demonstrating a REST API example&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Peter Niederwieser is &lt;a href="https://twitter.com/pniederw/status/513084438582800385">lobbying for fixing those issues in the IntelliJ IDEA Groovy support&lt;/a>. Please vote for them!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/ratpackweb/status/514200501890457600">Ratpack 0.9.9 onward will require Java 8&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/ratpackweb/status/514243729192779778">Ratpack ships new releases every first day of the month&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/ratpackweb/status/514066358959501313">Ratpack’s RxJava support in Java 7 vs Java 8&lt;/a>&lt;/li>
&lt;li>You can decorate your garden with &lt;a href="https://twitter.com/finistseb/status/511984302809444352">Groovy flowers&lt;/a>!&lt;/li>
&lt;li>Proper &lt;a href="https://twitter.com/cedricchampeau/status/512941179395399681">overloaded setter support&lt;/a> is coming to Groovy&lt;/li>
&lt;li>&lt;a href="https://twitter.com/_ericlobdell/status/513299775538864129">Groovy as a gateway drug into the JVM&lt;/a>&lt;/li>
&lt;li>With OpenJDK 8 out, &lt;a href="https://twitter.com/ratpackweb/status/512365480070221824">Ratpack&amp;rsquo;s next release will require Java 8&lt;/a>&lt;/li>
&lt;li>Benoît Hédiart mentions CodingGame.com: a &lt;a href="https://twitter.com/benorama/status/512541644269584384">coding game where you can program games in Groovy&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge is having fun coding an &lt;a href="https://twitter.com/glaforge/status/513782559919341568">Android application in Groovy for helping her daughter to learn how to read&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge on stage at &lt;a href="https://twitter.com/droidconfr/status/514073374742818816">DroidCon Paris 2014 evangelizing Groovy on Android&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/sbglasius/status/513834602746478592">GroovyBlogs now displays latest blog entries and most popular&lt;/a> ones&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>How to create &lt;a href="http://stackoverflow.com/questions/23614095/using-gradle-to-split-external-libraries-in-separated-dex-files-to-solve-android/25975702#25975702">multiple Android dex files with a Gradle trick&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>SpringOne2GX 2014 in Dallas is over. And next week starts JavaOne. It’s conference season! Speaking of which… the fine GR8Conf US crew released many more presentations this week! There’s a lot of content to go through!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/grooscript/status/512724108941881344">GrooScript 0.6</a> is out, on the road to 1.0, and Jorge Franco is looking for your feedback</li>
<li><a href="https://twitter.com/droidxav/status/512731018097209344">Gradle Android plugin 0.13</a> works with Gradle 2.1</li>
<li><a href="https://twitter.com/javabake/status/513793973857828864">JBake 2.3.2</a> with Asciidoctor 1.5</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://www.gradle.org/docs/2.1/release-notes#incremental-java-compilation">Incremental Java compilation landed in Gradle</a> 2.1</li>
<li><a href="http://grydeske.net/news/show/62">Grails Diary week 38</a> by Jacob Aae Mikkelsen</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://solidsoft.wordpress.com/2014/09/17/whats-new-in-upcoming-spock-1-0-simpler-conditional-test-execution-with-requires-and-ignoreif/">What&rsquo;s new in Spock 1.0</a>, by Marcin Zajączkowski</li>
<li><a href="http://mariogarcia.github.io/gpars-workshop/#_remoting">GPars remote dataflows and actors</a> by Mario García</li>
<li><a href="http://grooscript.org/react_example.html">Writing React.JS components in Groovy with GrooScript</a></li>
<li>The &ldquo;<a href="http://grailsrevolution.wordpress.com/2014/09/18/grails-revolution-the-force/">Grails revolution</a>&rdquo; movement is looking forward to spreading the word on how great Grails is</li>
<li>What&rsquo;s new with <a href="http://devsoap.com/#!/What-is-new-with-Gradle-Vaadin-Plugin-09">Gradle Vaadin Plugin 0.9</a>? Groovy support!</li>
<li>MrHaki&rsquo;s Gradle Goodness: <a href="http://mrhaki.blogspot.fr/2014/09/gradle-goodness-adding-dependencies.html">Adding dependencies only for packaging to war</a></li>
<li><a href="http://csiebler.github.io/blog/2014/02/09/multi-project-code-coverage-using-gradle-and-jacoco/">Multi-project code coverage using Gradle and JaCoCo</a> by Clemens Siebler</li>
<li>After his Hibernate/Grails series, Robert Peszek comes to functional programming: &ldquo;My dream: software without any bugs &hellip; and <a href="http://rpeszek.blogspot.fr/2014/09/my-dream-software-without-any-bugs-and.html">is Groovy functional? How about Grails</a>?&rdquo;</li>
<li>How to <a href="https://www.timroes.de/2013/09/12/speed-up-gradle/">speed up Gradle builds? Use the daemon</a>!</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Guillaume Laforge presented about <a href="https://speakerdeck.com/glaforge/groovy-on-android-droidcon-paris-2014">Groovy&rsquo;s Android support</a> at DroidCon Paris 2014</li>
<li><a href="https://www.youtube.com/watch?v=RuTupC0I59M&amp;feature=youtu.be">Idiomatic Spock</a> with Robert Fletcher recorded at Netflix</li>
<li>Trisha Gee gave her <a href="http://virtualjug.com/html5-angularjs-groovy-java-and-mongodb-all-together-what-could-go-wrong/">Groovy / Angular.JS / HTML 5 / MongoDB</a> talk to the Virtual JUG</li>
<li><a href="https://www.youtube.com/watch?v=f_fGwa2-rMk">Micro-services with Groovy and Spring Boot</a> by Marcin Grzejszczak</li>
<li>GR8Conf US 2014 presentations
<ul>
<li><a href="https://www.youtube.com/watch?v=0aYcLMJkv5c">Groovy at SmartThings</a> presented by Scott Vlaminck and Ryan Applegate</li>
<li>The <a href="https://www.youtube.com/watch?v=O0vt57XeF5E&amp;feature=youtu.be">Groovy Ecosystem revisited</a> by Andrés Almiray</li>
<li><a href="https://www.youtube.com/watch?v=g0fX5yA-Xzg&amp;feature=youtu.be">Getting started with Ratpack</a> presented by Jeff Beck</li>
<li><a href="https://www.youtube.com/watch?v=Ap16ueXSJig">Realtime Ratpack</a> by Robert Fletcher</li>
<li><a href="https://www.youtube.com/watch?v=G4EAd514jdE">Introduction to Gradle</a> by John Engelman</li>
<li><a href="https://www.youtube.com/watch?v=qyj7A-8n49w">Grails Goodness</a> presented by Hubert Klein Ikkink</li>
<li><a href="https://www.youtube.com/watch?v=erg5Ng8yuKQ">Gradle Goodness</a> presented by Hubert Klein Ikkink</li>
<li><a href="https://www.youtube.com/watch?v=u3KpiLZ1-64&amp;feature=youtu.be">Distributed platform development with Groovy</a> by Robert Fletcher</li>
<li><a href="https://www.youtube.com/watch?v=unUJwAAtLcM">Introduction to multithreaded programing with GPars</a> by Jon DeJong</li>
<li><a href="https://www.youtube.com/watch?v=ZYISj1RmQA0&amp;feature=youtu.be">Tour de plugin</a>, by Søren Berg Glasius</li>
<li><a href="https://www.youtube.com/watch?v=tSbx2MSsD5A">Grails Worst Practices</a> by Burt Beckwith</li>
<li><a href="https://www.youtube.com/watch?v=QhVCYLgHak4&amp;feature=youtu.be">Griffon, what&rsquo;s new and what&rsquo;s coming</a>, by Andrés Almiray</li>
<li><a href="https://www.youtube.com/watch?v=u8Ib2JFcyX8">How to test your Grails application</a> presented by Colin Harrington</li>
<li><a href="https://www.youtube.com/watch?v=UlJByqywIkE&amp;feature=youtu.be">Hybrid view rendering with Grails</a> by Robert Fletcher</li>
<li><a href="https://www.youtube.com/watch?v=lL_wQF5AjH8">Testing BitCoin and MasterCoin with Spock</a> by Sean Gilligan</li>
<li><a href="https://www.youtube.com/watch?v=EnyKt7NQTEY">Performance tuning Grails applications</a> by Lari Hotari</li>
<li><a href="https://www.youtube.com/watch?v=1umis5WS8_w">What&rsquo;s new in the Grails Spring security plugin</a> by Burt Beckwith</li>
<li><a href="https://www.youtube.com/watch?v=Rid_hOnJTrk">Application architectures in Grails</a> by Peter Ledbrook</li>
<li><a href="https://www.youtube.com/watch?v=dV-a21JQ21k">Idiomatic Spock</a> by Robert Fletcher</li>
<li><a href="https://www.youtube.com/watch?v=hHMaTZarRjY">Getting Grails into the Enterprise</a> by Ryan Vanderwerf</li>
<li><a href="https://www.youtube.com/watch?v=YG1hWfLDuBo">Metaprogramming with the Groovy Runtime</a> by Jeff Brown</li>
<li><a href="https://www.youtube.com/watch?v=OFFjqeqt8Ds">Getting Groovy with Graphs</a> by Stefan Armbruster</li>
<li><a href="https://www.youtube.com/watch?v=LyJ4fORkHfA">Serving Websockets to iOS, Android and the Web using Groovy, Spring, Angular.js, SockJs and STOMP</a> presented by Sean Gilligan</li>
</ul>
</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Bertrand Goetzmann publishes a screen cast of <a href="https://www.youtube.com/watch?v=QHGqxMpxqMU&amp;feature=youtu.be">Grails with the Ionic framework</a> demonstrating a REST API example</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Peter Niederwieser is <a href="https://twitter.com/pniederw/status/513084438582800385">lobbying for fixing those issues in the IntelliJ IDEA Groovy support</a>. Please vote for them!</li>
<li><a href="https://twitter.com/ratpackweb/status/514200501890457600">Ratpack 0.9.9 onward will require Java 8</a></li>
<li><a href="https://twitter.com/ratpackweb/status/514243729192779778">Ratpack ships new releases every first day of the month</a></li>
<li><a href="https://twitter.com/ratpackweb/status/514066358959501313">Ratpack’s RxJava support in Java 7 vs Java 8</a></li>
<li>You can decorate your garden with <a href="https://twitter.com/finistseb/status/511984302809444352">Groovy flowers</a>!</li>
<li>Proper <a href="https://twitter.com/cedricchampeau/status/512941179395399681">overloaded setter support</a> is coming to Groovy</li>
<li><a href="https://twitter.com/_ericlobdell/status/513299775538864129">Groovy as a gateway drug into the JVM</a></li>
<li>With OpenJDK 8 out, <a href="https://twitter.com/ratpackweb/status/512365480070221824">Ratpack&rsquo;s next release will require Java 8</a></li>
<li>Benoît Hédiart mentions CodingGame.com: a <a href="https://twitter.com/benorama/status/512541644269584384">coding game where you can program games in Groovy</a></li>
<li>Guillaume Laforge is having fun coding an <a href="https://twitter.com/glaforge/status/513782559919341568">Android application in Groovy for helping her daughter to learn how to read</a></li>
<li>Guillaume Laforge on stage at <a href="https://twitter.com/droidconfr/status/514073374742818816">DroidCon Paris 2014 evangelizing Groovy on Android</a></li>
<li><a href="https://twitter.com/sbglasius/status/513834602746478592">GroovyBlogs now displays latest blog entries and most popular</a> ones</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>How to create <a href="http://stackoverflow.com/questions/23614095/using-gradle-to-split-external-libraries-in-separated-dex-files-to-solve-android/25975702#25975702">multiple Android dex files with a Gradle trick</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #36</title><link>https://glaforge.dev/posts/2014/09/16/groovy-weekly-36/</link><pubDate>Tue, 16 Sep 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/09/16/groovy-weekly-36/</guid><description>&lt;p>The SpringOne2GX 2014 conference is over, but you can see below more tweets and presentations from the second part of the conference, and the videos will be available later on on InfoQ.&lt;/p>
&lt;p>Apart from the excitement around Grails 3, Spring Boot, Groovy, and more, I’d like to highlight the very interesting feedback from Scott Hickey who comes back on 6 years of production usage of Groovy at Mutual of Omaha, saying “Groovy is a phenomenal language for businnes applications”, and that there’s nothing “better” on the JVM! See the first couple of tweets in the Twitter section.&lt;/p></description><content:encoded>
<![CDATA[<p>The SpringOne2GX 2014 conference is over, but you can see below more tweets and presentations from the second part of the conference, and the videos will be available later on on InfoQ.</p>
<p>Apart from the excitement around Grails 3, Spring Boot, Groovy, and more, I’d like to highlight the very interesting feedback from Scott Hickey who comes back on 6 years of production usage of Groovy at Mutual of Omaha, saying “Groovy is a phenomenal language for businnes applications”, and that there’s nothing “better” on the JVM! See the first couple of tweets in the Twitter section.</p>
<h2 id="news">News</h2>
<ul>
<li>With Gradle 2.1, you can <a href="http://www.gradle.org/docs/2.1/release-notes#groovy-compiler-configuration-script-support">type check or statically compiled all your Groovy code by default</a></li>
<li>Oracle mentions briefly the <a href="https://blogs.oracle.com/henrik/entry/java_8_not_just_for">collaboration between Oracle and the Groovy development team to test JDK 8</a></li>
<li><a href="http://grydeske.net/news/show/61">Grails Diary</a> week 37 by Jacob Aae Mikkelsen</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Luke Daley explains the <a href="http://ldaley.com/post/97376696242/ratpack-execution-model-part-1">Ratpack execution model</a></li>
<li>Jeff Beck details <a href="http://beckje01.com/blog/2014/09/10/ratpack-promise/">Ratpack promises</a></li>
<li>Roberto Guerra dives into how to replicate an <a href="http://blog.stumblingoncode.com/posts/2014-09-11-ratpack-asset-processing.html">asset-pipeline for Ratpack</a></li>
<li>Mark Perry writes on <a href="http://mperry.github.io/2014/09/10/groovy-monad-combinators.html">Groovy Monad Combinators</a></li>
<li><a href="http://rpeszek.blogspot.fr/2014/09/i-dont-like-hibernategrails-part-7.html">I don&rsquo;t like Grails / Hibernate</a>, part 7, working on more complex projects</li>
<li><a href="http://www.itexto.net/devkico/?p=1957">Integrate Groovy and Java</a> by Henrique Lobo Weissmann in Portuguese</li>
<li>SpringOne2GX 2014 recap by Uday Pratap Singh
<ul>
<li><a href="http://www.intelligrape.com/blog/2014/09/09/day-1-at-springone2gx-2014-opening-night-keynotes/">Day #1</a></li>
<li><a href="http://www.intelligrape.com/blog/2014/09/10/day-2-at-springone-2gx-2014/">Day #2</a></li>
<li><a href="http://www.intelligrape.com/blog/2014/09/11/day-3-at-springone-2gx-2014/">Day #3</a></li>
<li><a href="http://www.intelligrape.com/blog/2014/09/12/day-4-at-springone-2gx-2014-the-finale/">Day #4</a></li>
</ul>
</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Andrew Reitz posts his <a href="https://twitter.com/andrewreitz_/status/509543532181610496">Groovy on Android</a> lightning talk slides</li>
<li>Andrés Almiray presented <a href="http://2014.javazone.no/presentation.html?id=afbc91ce">Functional Groovy</a> at the JavaZone conference</li>
<li>GR8Conf US 2014 videos
<ul>
<li>Kyle Boon on <a href="https://www.youtube.com/watch?v=nVT8Gw-7x_c&amp;feature=youtu.be">growing up with Grails</a></li>
<li>Ryan Applegate and Adam Lanners explain how to <a href="https://www.youtube.com/watch?v=x2-yF-L4gdQ">get Groovy with Vert.x</a></li>
</ul>
</li>
<li>SpringOne2GX 2014 presentations
<ul>
<li>Cédric Champeau posted the <a href="https://twitter.com/cedricchampeau/status/510166855994990593">slides and code of his Groovy on Android</a> presentation</li>
<li><a href="https://speakerdeck.com/graemerocher/making-spring-boot-groovier">Making Spring Boot groovier</a> by Graeme Rocher</li>
<li><a href="https://speakerdeck.com/graemerocher/advanced-gorm-beyond-relational">Advanced Grails GORM beyond relational</a> by Graeme Rocher</li>
<li>Graeme Rocher reveals a <a href="https://speakerdeck.com/graemerocher/grails-3-preview">preview of Grails 3.0</a></li>
<li>Marco Vermeulen presented <a href="http://marcovermeulen.github.io/cuke-groovy-talk/#/">Behavior-Driven-Development with Cucumber and Groovy</a></li>
<li>Marco Vermeulen posted the code and slides of his <a href="https://twitter.com/marcovermeulen/status/510089484545687552">Spring Boot + Groovy + Cucumber</a> talk</li>
<li>Stéphane Maldini posted the slides of his presentation on <a href="http://fr.slideshare.net/StphaneMaldini/spring-one2gx-2014reactivestreams">Reactor and Reactive Streams</a></li>
<li>Lari Hotari&rsquo;s <a href="https://twitter.com/lhotari/status/510569280606199808">performance tuning talk</a>: on unnecessary block operations</li>
<li>Lari Hotari&rsquo;s <a href="https://twitter.com/lhotari/status/510568222190354432">performance tuning presentation</a>: on CPU-hogging regular expressions</li>
<li>Fabrice Matrat shares his presentation on <a href="http://fabricematrat.github.io/grails-rave/">RaveJS and Grails</a></li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>SpringOne2GX 2014 Groovy BOF started with the case study of Groovy production usage at Mutual of Omaha</li>
<li>Testimonial from the <a href="https://twitter.com/glaforge/status/509533986897399808">6 years of Groovy in production at Mutual of Omaha</a>: &ldquo;We learned Groovy is better, period.&rdquo;</li>
<li>&ldquo;<a href="https://twitter.com/glaforge/status/509535048341520384">Groovy is a phenomenal language for complicated business applications</a>&rdquo; says Mutual of Omaha</li>
<li><a href="https://twitter.com/sbglasius/status/510075936964763648">Netflix is contributing heavily to the Gradle plugin ecosystem</a></li>
<li>Jack Frosch finds Groovy is getting better and better, especially with <a href="https://twitter.com/jackfrosch/status/509540543278235648">traits as a &ldquo;game changer&rdquo;</a></li>
<li>Stéphane Maldini mentions the ongoing <a href="https://twitter.com/smaldini/status/511171875649880065">collaboration between the Groovy development team and the Reactor team to make Reactor play nice with Groovy on Android</a></li>
<li>You can <a href="https://twitter.com/sbglasius/status/509839267711840256">send Groovy puzzlers</a> by email and through twitter</li>
<li>Szymon Stepniak shares a <a href="https://twitter.com/wololock/status/509605494869463040">recipe for having a good time: Groovy + GPars + Spring Boot</a></li>
<li>Andrés Almiray notes <a href="https://twitter.com/aalmiray/status/510010512726585344">issues with JDK 8 update 11 with Groovy and Cobertura</a> and decides to move to JaCoCo instead</li>
<li><a href="https://twitter.com/ratpackweb/status/510037090344501248">Ratpack is making promises transformable</a> making async easier</li>
<li><a href="https://twitter.com/gvmtool/status/510041384569409536">GVM asks your feedback in a survey</a> asking you if you prefer lite or full distributions when downloading candidates</li>
<li>Dierk König remarks that <a href="https://twitter.com/mittie/status/510099003779055616">Intellij IDEA shows Java constructor calls like Groovy named-parameter constructor calls</a></li>
<li>Dierk König teases with a <a href="https://twitter.com/mittie/status/511289143243337728">GroovyFX demo</a> for his upcoming JavaOne presentation</li>
<li>Cédric Champeau will be speaking about <a href="https://twitter.com/cedricchampeau/status/511909801811398656">Groovy on Android at SoftShake</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>SpringOne2GX 2014 <a href="https://github.com/SpringOne2GX-2014/reactive-geocoder">demo application combining Ratpack, Spring Boot, Reactor and its reactive streams on Java 8</a></li>
<li>Václav Pech illustrates a <a href="https://gist.github.com/vaclav/c93225b2f93263bb8c55">safe thread starter with a Groovy trait</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>The 3rd <a href="https://twitter.com/gradleware/status/511641094442860544">Gradle Summit</a> will be held June 11-12th in Santa Clara, California</li>
<li><a href="https://twitter.com/colinharrington/status/509754307009581056">SpringOne2GX 2015 will talk place in Washington DC</a> on September 14th-17th</li>
<li>The <a href="https://twitter.com/jennstrater/status/509758555142889472">GR8Ladies now have a monthly meetup</a>. Next talk on October 6th with an introduction to Groovy</li>
<li>The Austin Groovy Grails user group wraps up its <a href="http://www.meetup.com/Austin-Groovy-and-Grails-Users/events/206899722/">coverage of the SpringOne2GX 2014 conference</a> on September 24th</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li><a href="http://careers.stackoverflow.com/jobs/67807/160k-android-developer-work-with-android-l-averity">Work with RxJava, Groovy on Android for the New York Times</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #35</title><link>https://glaforge.dev/posts/2014/09/09/groovy-weekly-34/</link><pubDate>Tue, 09 Sep 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/09/09/groovy-weekly-34/</guid><description>&lt;p>Direct live from &lt;a href="http://springone2gx.com/">SpringOne2GX&lt;/a>, in Dallas! So you can expect some news and presentations from the show this week and the next, and certainly tons of tweets!&lt;/p>
&lt;p>Speaking of tweets from SpringOne2GX, you’ll notice the flurry of very positive and excited reactions regarding the upcoming Grails 3 release.&lt;/p>
&lt;p>In the release section, don’t miss the release of Gradle 2.1.&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://forums.gradle.org/gradle/topics/gradle-2-1-released">Gradle 2.1&lt;/a> released with community plugins, and incremental java compilation&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/506086805759066112">GVM 2.1&lt;/a> released with improved self-updates&lt;/li>
&lt;li>&lt;a href="https://github.com/Arasthel/SwissKnife">Swiss Army&lt;/a>, a handy library for Groovy on Android, covering view injection, threading and more, by Jorge Martín&lt;/li>
&lt;li>&lt;a href="http://forums.gradle.org/gradle/topics/gradle-2-1-rc-4-is-now-available-for-testing">Gradle 2.1 rc-4&lt;/a> is ready for testing&lt;/li>
&lt;li>&lt;a href="https://spring.io/blog/2014/09/05/spring-boot-1-1-6-released">Spring Boot 1.1.6&lt;/a> released&lt;/li>
&lt;li>A &lt;a href="https://github.com/robertpanzer/asciidoctorj-groovy-dsl">Groovy DSL that allows for easy definition of Asciidoctor extensions&lt;/a> by Robert Panzer&lt;/li>
&lt;li>&lt;a href="https://twitter.com/aalmiray/status/507818302496702465">Asciidoctor Gradle Template 1.0.0&lt;/a> announced by Andrés Almiray&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://www.groovyblogs.org/">GroovyBlogs&lt;/a> is coming back to life thanks to Søren Berg Glasius&lt;/li>
&lt;li>Jacob Aae Mikkelsen &lt;a href="http://grydeske.net/news/show/60">Grails Diary week 36&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://docs.codehaus.org/display/GPARS/2014/09/06/Remoting+for+GPars">Remoting comes to GPars&lt;/a>, thanks to Google Summer of Code Rafał Sławik&amp;rsquo;s great work&lt;/li>
&lt;li>&lt;a href="http://kyleboon.org/blog/2014/08/14/ratpack-plus-docker-plus-gradle/">Ratpack + Docker + Gradle&lt;/a> by Kyle Boon&lt;/li>
&lt;li>Benjamin Muschko speaks about &lt;a href="http://www.gradleware.com/tutorials/feature-spotlight-gradles-support-maven-pom-profiles/">Gradle&amp;rsquo;s support of Maven POM profiles&lt;/a>&lt;/li>
&lt;li>&amp;ldquo;I don&amp;rsquo;t like Grails / Hibernate&amp;rdquo; goes on, covering &lt;a href="http://rpeszek.blogspot.fr/2014/09/i-dont-like-hibernategrails-part-6-how.html">how to save objects using refresh()&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://www.gradleware.com/conferences/gradle-summit-2014-video-gradle-enterprise-linkedin/">Gradle in the enterprise at LinkedIn&lt;/a> at Gradle Summit 2014&lt;/li>
&lt;li>&lt;a href="http://www.parleys.com/play/530b52fbe4b085f68c6b653f/chapter0/about">Benefit from Groovy, when, why, and how&lt;/a>, presented by Guillaume Laforge at JFokus 2014&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/ShlomiBenHaim/status/508772445852278784">Groovy way to write Jenkins CI plugins&lt;/a> by Shiran Rubin&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/fr/presentations/programmation-fonctionnelle-groovy">Functional programming in Groovy&lt;/a>, by Guillaume Laforge, recorded at BreizhCamp 2014 (in French)&lt;/li>
&lt;li>&lt;a href="http://jaxenter.de/videos/funktionen-monaden-groovy-jaxtv-175429">How to get fully functional in Groovy&lt;/a>, by Dierk König (in German)&lt;/li>
&lt;li>Video tutorial how &lt;a href="http://www.gradleware.com/conferences/gradle-summit-2014-video-gradle-ides/">Gradle is integrated in Java IDEs&lt;/a>&lt;/li>
&lt;li>A &lt;a href="http://www.java-tv.com/2014/08/28/groovy-introduction/">Groovy introduction presentation&lt;/a> by Joachim Baumann&lt;/li>
&lt;li>First slide decks published from SpringOne2GX 2014 in Dallas, Texas:
&lt;ul>
&lt;li>Cédric Champeau dived into &lt;a href="https://speakerdeck.com/melix/rethinking-api-design-with-traits">rethinking API design with Groovy traits&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge presented the &lt;a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-at-springone2gx-2014">latest state of the Groovy nation&lt;/a> at SpringOne2GX 2014&lt;/li>
&lt;li>Guillaume Laforge answers the question wheter &lt;a href="https://speakerdeck.com/glaforge/groovy-in-the-light-of-java-8-springone2gx-2014">Groovy is still relevant now that we have Java 8&lt;/a>&lt;/li>
&lt;li>Paul King speaks about &lt;a href="http://fr.slideshare.net/paulk_asert/groovy-databases">working with databases in Groovy&lt;/a>&lt;/li>
&lt;li>Paul King covers &lt;a href="http://fr.slideshare.net/paulk_asert/groovy-transforms">Groovy AST transformations&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau will present &amp;ldquo;&lt;a href="https://twitter.com/cedricchampeau/status/507130051909808130">Rethinking API design with traits&lt;/a>&amp;rdquo; at JavaOne&lt;/li>
&lt;li>Andrés Almiray will &lt;a href="https://twitter.com/aalmiray/status/507263810932310016">evangelize Gradle at the Jazoon&lt;/a> conference&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/507862222585200640">Groovy 2.4 beta 3&lt;/a> available on GVM&lt;/li>
&lt;li>Søren Berg Glasius tells us that &lt;a href="https://twitter.com/sbglasius/status/507726726697218048">GroovyBlogs is coming back to life&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/507663212918349824">Gradle 2.1-rc-4&lt;/a> is available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/msgilligan/status/503365901052149760">Groovy had Peter Ledbrook at &amp;ldquo;properties&amp;rdquo;&lt;/a>&lt;/li>
&lt;li>Dan Woods believes &lt;a href="https://twitter.com/danveloper/status/502900825161601024">Ratpack&amp;rsquo;s handler chain and Groovy DSL is the best combo for building restful APIs&lt;/a>&lt;/li>
&lt;li>You can &lt;a href="https://twitter.com/gvmtool/status/502358588539826176">customize the GVM self-update prompt&lt;/a>&lt;/li>
&lt;li>Ted Naleid notes that the &lt;a href="https://twitter.com/tednaleid/status/501881682178220033">@DirtiesRuntime test-level annotation can mark tests with metaclass changes in Grails&lt;/a> 2.4.4&lt;/li>
&lt;li>Even during the SpringOne2GX keynote, &lt;a href="https://twitter.com/ryanvanderwerf/status/509130411125919744">GVM makes the show&lt;/a>!&lt;/li>
&lt;li>&lt;a href="https://twitter.com/cedricchampeau/status/509350223521406976">All the new Groovy documentation is tested&lt;/a>: all snippets are included from test cases.&lt;/li>
&lt;li>Lots of Grails 3 related tweets following up Graeme Rochere’s Grails 3 preview at SpringOne2GX 2014
&lt;ul>
&lt;li>The &lt;a href="https://twitter.com/rfletcherew/status/509374166555176960">Grails 3 preview gets developers excited&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/509342714249220096">Spring Boot 1.2.0.M1&lt;/a> is available on GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/509371622143184896">Grails 3 is abandoning Gant in favor of Gradle&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/509139062561599489">Goals for Grails 3&lt;/a>: support a plugin model, better eventing, better micro-services, based off Spring Boot, and more&lt;/li>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/509139609997950976">Grails 3 will make a clean delineation between plugins&lt;/a>, distinguishing code generation, build-time, runtime&lt;/li>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/509373452277805056">Grails 3 apps can be packaged as runnable JARs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/509382811376041986">Grails 3 will be independent of the Servlet API&lt;/a>&lt;/li>
&lt;li>You can &lt;a href="https://twitter.com/danveloper/status/509375965743431680">create micro-Grails applications that as simple Groovy scripts&lt;/a>&lt;/li>
&lt;li>Grails 3 is going to feature &lt;a href="https://twitter.com/danveloper/status/509380846319456257">application profiles&lt;/a>&lt;/li>
&lt;li>Grails 3 will allow you to &lt;a href="https://twitter.com/pledbrook/status/509380623312502784">debug your controllers&lt;/a> more easily as they are now Groovy traits&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/danveloper/status/509383407516647424">roadmap for Grails 3 is to have a first milestone before the end of the year&lt;/a> and the GA before next year&amp;rsquo;s SpringOne2GX, a year from now.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://grooscript.org/demo/bezier.html">Painting like Jackson Pollock&lt;/a>, in Groovy, in the browser, thanks to GrooScript with this demo from Jorge Franco Leza&lt;/li>
&lt;li>Graeme Rocher wowed the crowds at SpringOne2GX&amp;rsquo;s keynote with a &lt;a href="https://twitter.com/danveloper/status/509141002838220800">tweetable full data-driven rest application with Grails 3&lt;/a>&lt;/li>
&lt;li>Cédric Champeau shares a Gradle tip to &lt;a href="https://gist.github.com/melix/60d9f94a2ab961c1a0da">bypass odd VerifyErrors when you&amp;rsquo;re developing Android applications in Groovy&lt;/a>&lt;/li>
&lt;li>Lari Hotari contributed &lt;a href="https://github.com/ratpack/ratpack/pull/433">Spring Boot support for Ratpack&lt;/a>&lt;/li>
&lt;li>Schalk Cronjé shares a tip on &lt;a href="https://gist.github.com/ysb33r/68ba7ad1279d0492086a">how control logging running tests in Gradle&lt;/a>&lt;/li>
&lt;li>Stéphane Nicoll shows a neat &lt;a href="https://twitter.com/snicoll/status/507924858823786496">Spring Boot rabbit support from Groovy&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/509374137622482946">Grails 3 and Spring Boot integration&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://cfp.devoxx.be/2014">Devoxx 2014 will feature Groovy&lt;/a> with Cédric Champeau speaking of Groovy on Android, and Guillaume Laforge on Groovy in the light of Java 8&lt;/li>
&lt;li>Phil Webb will be talking about &lt;a href="http://www.meetup.com/San-Francisco-Grails-Centro/events/205421502/">Spring Boot at the Groovy Grails Centro meetup on September 23rd&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge is going to speak about &lt;a href="https://twitter.com/vfabric/status/509324343982424065">Groovy in the light of Java 8 at JavaOne&lt;/a> 2014&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>Interested in beta-testing an &lt;a href="https://twitter.com/ErinWith2Ls/status/506826876266889216">introductory Groovy and Grails book&lt;/a>?&lt;/li>
&lt;li>&lt;a href="https://twitter.com/manningbooks/status/509359104692084736">44% off of Manning’s Groovy, Grails, Spring books&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Direct live from <a href="http://springone2gx.com/">SpringOne2GX</a>, in Dallas! So you can expect some news and presentations from the show this week and the next, and certainly tons of tweets!</p>
<p>Speaking of tweets from SpringOne2GX, you’ll notice the flurry of very positive and excited reactions regarding the upcoming Grails 3 release.</p>
<p>In the release section, don’t miss the release of Gradle 2.1.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-1-released">Gradle 2.1</a> released with community plugins, and incremental java compilation</li>
<li><a href="https://twitter.com/gvmtool/status/506086805759066112">GVM 2.1</a> released with improved self-updates</li>
<li><a href="https://github.com/Arasthel/SwissKnife">Swiss Army</a>, a handy library for Groovy on Android, covering view injection, threading and more, by Jorge Martín</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-1-rc-4-is-now-available-for-testing">Gradle 2.1 rc-4</a> is ready for testing</li>
<li><a href="https://spring.io/blog/2014/09/05/spring-boot-1-1-6-released">Spring Boot 1.1.6</a> released</li>
<li>A <a href="https://github.com/robertpanzer/asciidoctorj-groovy-dsl">Groovy DSL that allows for easy definition of Asciidoctor extensions</a> by Robert Panzer</li>
<li><a href="https://twitter.com/aalmiray/status/507818302496702465">Asciidoctor Gradle Template 1.0.0</a> announced by Andrés Almiray</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://www.groovyblogs.org/">GroovyBlogs</a> is coming back to life thanks to Søren Berg Glasius</li>
<li>Jacob Aae Mikkelsen <a href="http://grydeske.net/news/show/60">Grails Diary week 36</a></li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://docs.codehaus.org/display/GPARS/2014/09/06/Remoting+for+GPars">Remoting comes to GPars</a>, thanks to Google Summer of Code Rafał Sławik&rsquo;s great work</li>
<li><a href="http://kyleboon.org/blog/2014/08/14/ratpack-plus-docker-plus-gradle/">Ratpack + Docker + Gradle</a> by Kyle Boon</li>
<li>Benjamin Muschko speaks about <a href="http://www.gradleware.com/tutorials/feature-spotlight-gradles-support-maven-pom-profiles/">Gradle&rsquo;s support of Maven POM profiles</a></li>
<li>&ldquo;I don&rsquo;t like Grails / Hibernate&rdquo; goes on, covering <a href="http://rpeszek.blogspot.fr/2014/09/i-dont-like-hibernategrails-part-6-how.html">how to save objects using refresh()</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li><a href="http://www.gradleware.com/conferences/gradle-summit-2014-video-gradle-enterprise-linkedin/">Gradle in the enterprise at LinkedIn</a> at Gradle Summit 2014</li>
<li><a href="http://www.parleys.com/play/530b52fbe4b085f68c6b653f/chapter0/about">Benefit from Groovy, when, why, and how</a>, presented by Guillaume Laforge at JFokus 2014</li>
<li>The <a href="https://twitter.com/ShlomiBenHaim/status/508772445852278784">Groovy way to write Jenkins CI plugins</a> by Shiran Rubin</li>
<li><a href="http://www.infoq.com/fr/presentations/programmation-fonctionnelle-groovy">Functional programming in Groovy</a>, by Guillaume Laforge, recorded at BreizhCamp 2014 (in French)</li>
<li><a href="http://jaxenter.de/videos/funktionen-monaden-groovy-jaxtv-175429">How to get fully functional in Groovy</a>, by Dierk König (in German)</li>
<li>Video tutorial how <a href="http://www.gradleware.com/conferences/gradle-summit-2014-video-gradle-ides/">Gradle is integrated in Java IDEs</a></li>
<li>A <a href="http://www.java-tv.com/2014/08/28/groovy-introduction/">Groovy introduction presentation</a> by Joachim Baumann</li>
<li>First slide decks published from SpringOne2GX 2014 in Dallas, Texas:
<ul>
<li>Cédric Champeau dived into <a href="https://speakerdeck.com/melix/rethinking-api-design-with-traits">rethinking API design with Groovy traits</a></li>
<li>Guillaume Laforge presented the <a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-at-springone2gx-2014">latest state of the Groovy nation</a> at SpringOne2GX 2014</li>
<li>Guillaume Laforge answers the question wheter <a href="https://speakerdeck.com/glaforge/groovy-in-the-light-of-java-8-springone2gx-2014">Groovy is still relevant now that we have Java 8</a></li>
<li>Paul King speaks about <a href="http://fr.slideshare.net/paulk_asert/groovy-databases">working with databases in Groovy</a></li>
<li>Paul King covers <a href="http://fr.slideshare.net/paulk_asert/groovy-transforms">Groovy AST transformations</a></li>
</ul>
</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau will present &ldquo;<a href="https://twitter.com/cedricchampeau/status/507130051909808130">Rethinking API design with traits</a>&rdquo; at JavaOne</li>
<li>Andrés Almiray will <a href="https://twitter.com/aalmiray/status/507263810932310016">evangelize Gradle at the Jazoon</a> conference</li>
<li><a href="https://twitter.com/gvmtool/status/507862222585200640">Groovy 2.4 beta 3</a> available on GVM</li>
<li>Søren Berg Glasius tells us that <a href="https://twitter.com/sbglasius/status/507726726697218048">GroovyBlogs is coming back to life</a></li>
<li><a href="https://twitter.com/gvmtool/status/507663212918349824">Gradle 2.1-rc-4</a> is available on GVM</li>
<li><a href="https://twitter.com/msgilligan/status/503365901052149760">Groovy had Peter Ledbrook at &ldquo;properties&rdquo;</a></li>
<li>Dan Woods believes <a href="https://twitter.com/danveloper/status/502900825161601024">Ratpack&rsquo;s handler chain and Groovy DSL is the best combo for building restful APIs</a></li>
<li>You can <a href="https://twitter.com/gvmtool/status/502358588539826176">customize the GVM self-update prompt</a></li>
<li>Ted Naleid notes that the <a href="https://twitter.com/tednaleid/status/501881682178220033">@DirtiesRuntime test-level annotation can mark tests with metaclass changes in Grails</a> 2.4.4</li>
<li>Even during the SpringOne2GX keynote, <a href="https://twitter.com/ryanvanderwerf/status/509130411125919744">GVM makes the show</a>!</li>
<li><a href="https://twitter.com/cedricchampeau/status/509350223521406976">All the new Groovy documentation is tested</a>: all snippets are included from test cases.</li>
<li>Lots of Grails 3 related tweets following up Graeme Rochere’s Grails 3 preview at SpringOne2GX 2014
<ul>
<li>The <a href="https://twitter.com/rfletcherew/status/509374166555176960">Grails 3 preview gets developers excited</a></li>
<li><a href="https://twitter.com/gvmtool/status/509342714249220096">Spring Boot 1.2.0.M1</a> is available on GVM</li>
<li><a href="https://twitter.com/danveloper/status/509371622143184896">Grails 3 is abandoning Gant in favor of Gradle</a></li>
<li><a href="https://twitter.com/danveloper/status/509139062561599489">Goals for Grails 3</a>: support a plugin model, better eventing, better micro-services, based off Spring Boot, and more</li>
<li><a href="https://twitter.com/danveloper/status/509139609997950976">Grails 3 will make a clean delineation between plugins</a>, distinguishing code generation, build-time, runtime</li>
<li><a href="https://twitter.com/danveloper/status/509373452277805056">Grails 3 apps can be packaged as runnable JARs</a></li>
<li><a href="https://twitter.com/danveloper/status/509382811376041986">Grails 3 will be independent of the Servlet API</a></li>
<li>You can <a href="https://twitter.com/danveloper/status/509375965743431680">create micro-Grails applications that as simple Groovy scripts</a></li>
<li>Grails 3 is going to feature <a href="https://twitter.com/danveloper/status/509380846319456257">application profiles</a></li>
<li>Grails 3 will allow you to <a href="https://twitter.com/pledbrook/status/509380623312502784">debug your controllers</a> more easily as they are now Groovy traits</li>
<li>The <a href="https://twitter.com/danveloper/status/509383407516647424">roadmap for Grails 3 is to have a first milestone before the end of the year</a> and the GA before next year&rsquo;s SpringOne2GX, a year from now.</li>
</ul>
</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li><a href="http://grooscript.org/demo/bezier.html">Painting like Jackson Pollock</a>, in Groovy, in the browser, thanks to GrooScript with this demo from Jorge Franco Leza</li>
<li>Graeme Rocher wowed the crowds at SpringOne2GX&rsquo;s keynote with a <a href="https://twitter.com/danveloper/status/509141002838220800">tweetable full data-driven rest application with Grails 3</a></li>
<li>Cédric Champeau shares a Gradle tip to <a href="https://gist.github.com/melix/60d9f94a2ab961c1a0da">bypass odd VerifyErrors when you&rsquo;re developing Android applications in Groovy</a></li>
<li>Lari Hotari contributed <a href="https://github.com/ratpack/ratpack/pull/433">Spring Boot support for Ratpack</a></li>
<li>Schalk Cronjé shares a tip on <a href="https://gist.github.com/ysb33r/68ba7ad1279d0492086a">how control logging running tests in Gradle</a></li>
<li>Stéphane Nicoll shows a neat <a href="https://twitter.com/snicoll/status/507924858823786496">Spring Boot rabbit support from Groovy</a></li>
<li><a href="https://twitter.com/danveloper/status/509374137622482946">Grails 3 and Spring Boot integration</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="http://cfp.devoxx.be/2014">Devoxx 2014 will feature Groovy</a> with Cédric Champeau speaking of Groovy on Android, and Guillaume Laforge on Groovy in the light of Java 8</li>
<li>Phil Webb will be talking about <a href="http://www.meetup.com/San-Francisco-Grails-Centro/events/205421502/">Spring Boot at the Groovy Grails Centro meetup on September 23rd</a></li>
<li>Guillaume Laforge is going to speak about <a href="https://twitter.com/vfabric/status/509324343982424065">Groovy in the light of Java 8 at JavaOne</a> 2014</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Interested in beta-testing an <a href="https://twitter.com/ErinWith2Ls/status/506826876266889216">introductory Groovy and Grails book</a>?</li>
<li><a href="https://twitter.com/manningbooks/status/509359104692084736">44% off of Manning’s Groovy, Grails, Spring books</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Beta 3 for Groovy 2.4</title><link>https://glaforge.dev/posts/2014/09/02/beta-3-for-groovy-2-4/</link><pubDate>Tue, 02 Sep 2014 02:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/09/02/beta-3-for-groovy-2-4/</guid><description>&lt;p>The Groovy development team is happy to announce the release of a third beta for Groovy 2.4.&lt;/p>
&lt;p>This release contains various bug fixes and minor improvements.&lt;/p>
&lt;p>I&amp;rsquo;ll highlight the fact that you can now use multiple labels on your statements (instead of just one).&lt;/p>
&lt;p>With beta-3 now, many of the unneeded synthetic methods generated by Groovy are not generated anymore, reducing the number of methods nicely yielding smaller bytecode. This is of particular interest for Android developers limited in the overall number of methods used in Android applications.&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy development team is happy to announce the release of a third beta for Groovy 2.4.</p>
<p>This release contains various bug fixes and minor improvements.</p>
<p>I&rsquo;ll highlight the fact that you can now use multiple labels on your statements (instead of just one).</p>
<p>With beta-3 now, many of the unneeded synthetic methods generated by Groovy are not generated anymore, reducing the number of methods nicely yielding smaller bytecode. This is of particular interest for Android developers limited in the overall number of methods used in Android applications.</p>
<p>You can read the <a href="https://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20544">JIRA release notes</a> and go straight to the <a href="http://beta.groovy-lang.org/download.html">download area</a>!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #34 back from vacations!</title><link>https://glaforge.dev/posts/2014/09/02/groovy-weekly-34-back-from-vacations/</link><pubDate>Tue, 02 Sep 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/09/02/groovy-weekly-34-back-from-vacations/</guid><description>&lt;p>After a long summer break, it’s high time we resume Groovy Weekly, and come back to our every-Tuesday agenda! Get ready for lots of content!&lt;/p>
&lt;p>If there were one particular item of news I’d like to highlight in this edition, that’s the news about the &lt;a href="http://open.blogs.nytimes.com/2014/08/18/getting-groovy-with-reactive-android/?_php=true&amp;amp;_type=blogs&amp;amp;_r=0">New York Times that is going to use Groovy and its Android support&lt;/a> (in the upcoming Groovy 2.4), as well as RxJava, to revamp its Android application to make more “reactive”.&lt;/p></description><content:encoded>
<![CDATA[<p>After a long summer break, it’s high time we resume Groovy Weekly, and come back to our every-Tuesday agenda! Get ready for lots of content!</p>
<p>If there were one particular item of news I’d like to highlight in this edition, that’s the news about the <a href="http://open.blogs.nytimes.com/2014/08/18/getting-groovy-with-reactive-android/?_php=true&amp;_type=blogs&amp;_r=0">New York Times that is going to use Groovy and its Android support</a> (in the upcoming Groovy 2.4), as well as RxJava, to revamp its Android application to make more “reactive”.</p>
<p>Speaking of Groovy 2.4, it’s interesting to note that the new beta released today reduces the bytecode size, by not generating unneeded methods anymore. This should make Android developers happy, since they are overal limited in the number of methods in Android applications.</p>
<p>Next week will be pretty Groovy in Dallas, as the SpringOne2GX conference will be on. Perhaps we won’t have yet much news and presentations to share next Tuesday, but the following week will list all the great content and news happening at the conference. If you’re around, don’t hesitate to tell hi!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://glaforge.dev/posts/2014/09/02/beta-3-for-groovy-2-4/">Groovy 2.4-beta-3</a> released with reduced synthetic methods (particularly handy for Android developers) to reduce the bytecode size</li>
<li><a href="http://www.ratpack.io/versions/0.9.8">Ratpack 0.9.8</a> released with Ratpack&rsquo;s HttpClient testing support, dangling request detection, and reactive stream support</li>
<li><a href="http://docs.codehaus.org/display/GRIFFON/2014/08/11/Griffon+2.0.0.RC2+Released">Griffon 2.0 RC-2</a> released</li>
<li><a href="http://forums.gradle.org/gradle/topics/gradle-2-1-rc-3-is-now-available-for-testing">Gradle 2.1 RC-3</a> is available for testing</li>
<li><a href="https://bintray.com/chiquitinxx/grooscript/org.grooscript%3Agrooscript-gradle-plugin/0.5.1">Grooscript Gradle plugin 0.5.1</a> is out</li>
<li><a href="https://github.com/grails-samples/grails-petclinic">Grails Petclinic sample</a> app has been updated to 2.4.3 and now uses the asset-pipeline plugin and has new tests for the Domain classes.</li>
<li><a href="http://www.gdub.rocks/">Gdub, an enhanced Gradle wrapper</a></li>
<li><a href="https://code.google.com/p/jlabgroovy/wiki/JavaFXChartsInGroovyLab">JavaFX charting support for GroovyLab</a></li>
<li>Fabiano Taioli created a <a href="http://fbn.github.io/gimple/">micro Groovy, thread-safe, dependency injection container</a>, called Gimple</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="http://open.blogs.nytimes.com/2014/08/18/getting-groovy-with-reactive-android/?_php=true&amp;_type=blogs&amp;_r=0">New York Times is going Groovy with Reactive Android</a> by Mohit Panday</li>
<li>The Groovy project on Github has just crossed the <a href="https://github.com/groovy/groovy-core/pulls">500th pull requests</a> mark. Still some work to close the 25 still open though! Keep&rsquo;em coming, it&rsquo;s a wonderful way to contribute to the project!</li>
<li>The Grails Diary by Jacob Aae Mikkelsen
<ul>
<li><a href="http://grydeske.net/news/show/56">Week 32</a></li>
<li><a href="http://grydeske.net/news/show/57">Week 33</a></li>
<li><a href="http://grydeske.net/news/show/58">Week 34</a></li>
<li><a href="http://grydeske.net/news/show/59">Week 35</a></li>
</ul>
</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li><a href="http://www.objectpartners.com/2014/08/25/gr8conf-us-recap-why-your-company-should-adopt-groovy/">Why your company should be using Groovy</a>? Scott Hickey comes back on the Mutual of Omaha Groovy case study</li>
<li>Mark Perry on <a href="http://mperry.github.io/2014/08/19/groovy-monads.html">Groovy Monads</a></li>
<li><a href="http://grooscript.org/nodejs_example.html">Using the GrooScript Node.js support</a></li>
<li>Robert Fletcher uses <a href="http://blog.freeside.co/2014/08/07/spock-and-hamcrest/">Hamcrest with Spock</a></li>
<li><a href="http://blog.freeside.co/2014/08/11/multiple-interface-mocks-with-spock/">Multiple interface mocks with Spock</a> by Rob Fletcher</li>
<li><a href="http://swalsh.org/blog/2014/08/26/ratpack-first-impressions/">Ratpack: First Impressions</a>, by Sean Walsh</li>
<li>Andrés Almiray&rsquo;s Gradle glam: <a href="http://www.jroller.com/aalmiray/entry/gradle_glam_custom_asciidoctor_extensions">custom Asciidoctor extensions</a> in Groovy</li>
<li>MrHaki’s Asciidoctor goodness: <a href="http://mrhaki.blogspot.fr/2014/08/awesome-asciidoc-write-extensions-using.html">writing extensions in Groovy</a></li>
<li>MrHaki&rsquo;s Groovy goodness
<ul>
<li>use <a href="http://mrhaki.blogspot.fr/2014/08/groovy-goodness-use-custom-template.html">custom template class with MarkupTemplateEngine</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/08/groovy-goodness-nested-templates-with.html">nested templates with MarkupTemplateEngine</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/08/groovy-goodness-using-layouts-with.html">using layouts with MarkupTemplateEngine</a></li>
</ul>
</li>
<li>MrHaki&rsquo;s Gradle goodness
<ul>
<li><a href="http://mrhaki.blogspot.fr/2014/08/gradle-goodness-getting-more-dependency.html">getting more dependency insight</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/08/gradle-goodness-suppress-progress.html">suppress progress logging</a></li>
<li><a href="http://www.cholick.com/entry/show/280">Eliminating development redeploys</a> using Gradle</li>
</ul>
</li>
<li>Romin Irani’s Gradle tutorial series
<ul>
<li>Part 4: <a href="http://rominirani.com/2014/08/12/gradle-tutorial-part-4-java-web-applications/">Java web applications</a></li>
<li>Part 5: <a href="http://rominirani.com/2014/08/15/gradle-tutorial-part-5-gradle-app-engine-plugin/">Google AppEngine plugin</a></li>
<li>Part 6: <a href="http://rominirani.com/2014/08/19/gradle-tutorial-part-6-android-studio-gradle/">Android Studio and Gradle</a></li>
<li>Part 7: <a href="http://rominirani.com/2014/08/20/gradle-tutorial-part-7-android-studio-app-engine-gradle/">Android Studio, Gradle and Google AppEngine</a></li>
<li>Part 8: <a href="http://rominirani.com/2014/08/25/gradle-tutorial-part-8-gradle-app-engine-endpoints-android-studio/">Gradle, Google AppEngine, Endpoints and Android Studio</a></li>
<li>Part 9: <a href="http://rominirani.com/2014/08/26/gradle-tutorial-part-9-cloud-endpoints-persistence-android-studio/">Cloud Endpoints, persistence and Android Studio</a></li>
<li>Part 10: <a href="http://rominirani.com/2014/08/27/gradle-tutorial-part-10-consuming-endpoints-in-android-code/">Consuming Cloud Endpoints in Android Studio</a></li>
</ul>
</li>
<li>Robert Peszek&rsquo;s Grails/Hibernate series:
<ul>
<li>Part 3: <a href="http://rpeszek.blogspot.fr/2014/08/i-dont-like-grailshibernate-part-3.html">DuplicateKeyException, catch it if you can</a></li>
<li>Part 4: <a href="http://rpeszek.blogspot.fr/2014/08/i-dont-like-grailshibernat-part-4.html">Hibernate proxy objects</a></li>
<li>Part 5: <a href="http://rpeszek.blogspot.fr/2014/08/i-dont-like-hibernategrails-part-5-auto.html">auto-saving and auto-flushing</a></li>
</ul>
</li>
<li><a href="http://www.objectpartners.com/2014/08/19/add-javascript-unit-tests-and-run-them-with-grails-test-app/">Add Javascript unit tests and run them with “grails test-app”</a> by Igor Shults</li>
<li>Dmitriy Drenkalyuk covers <a href="http://sysgears.com/articles/advanced-gorm-features-inheritance-embedded-data-maps-and-lists-storing/">advanced GORM features</a>: inheritance, embedded data, maps and lists storing</li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Venkat Subramaniam reveals <a href="https://twitter.com/agilelearner/status/496970094946562048">how Groovy named parameter constructor works</a></li>
<li>GR8Conf US 2014 videos
<ul>
<li><a href="https://www.youtube.com/watch?v=KvBjO4WXL24&amp;feature=youtu.be">DevOps, Chef and Grails</a>, presented by Ken Liu</li>
<li><a href="https://www.youtube.com/watch?v=k6vXQwxk7N8&amp;feature=youtu.be">Groovy Puzzlers</a> presentation by Baruch Sadogursky and Andrés Almiray</li>
<li><a href="https://www.youtube.com/watch?v=oF-gK-x8RGw&amp;feature=youtu.be">Why your build matters</a>, by Peter Ledbrook</li>
<li><a href="https://www.youtube.com/watch?v=ROYQlc-wVEg">One build to rule them all</a>, by John Engelman</li>
<li><a href="https://www.youtube.com/watch?v=bTRUC78X87g">REPL driven development with the Grails console</a> plugin, by David Kuster</li>
</ul>
</li>
<li>A paid video on <a href="https://www.packtpub.com/effective-gradle-implementation/video">effective Gradle implementation</a> by Lee Fox and Ryan Vanderwerf for Packt Publishing</li>
</ul>
<h2 id="google-posts">Google+ posts</h2>
<ul>
<li>Richard Vowles releases a <a href="https://plus.google.com/u/0/+RichardVowles/posts/fQzj2Zfn8Ux">new version of his Maven Groovydoc plugin</a></li>
<li>Al Baker reports about <a href="https://plus.google.com/b/113675159854671799959/+AlBakerDev/posts/11DxyvNoKi7?cfem=1">Groovy&rsquo;s presence the next semantic web conference</a> with GroovySparql and Stardog-Groovy</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>GVM 2.0 is rolling out a <a href="https://twitter.com/gvmtool/status/497521264094093313">new broadcast API</a></li>
<li><a href="https://twitter.com/gvmtool/status/497490734980005889">Spring Boot 1.1.5</a> is available through GVM</li>
<li><a href="https://twitter.com/gvmtool/status/505331657508134912">Gradle 2.1 RC-2</a> is available on GVM</li>
<li>The <a href="https://twitter.com/pledbrook/status/503823613536251904">Lazybones Gradle plugin</a> is now in the Gradle plugin portal</li>
<li><a href="https://twitter.com/gvmtool/status/503591205071233024">Lazybones 0.7.1</a> is on GVM</li>
<li>Cédric Champeau is <a href="https://twitter.com/cedricchampeau/status/503834637203173378">gathering feedback about Groovy traits in production</a> for his SpringOne2GX presentation</li>
<li>Cédric Champeau will be speaking about <a href="https://twitter.com/cedricchampeau/status/504599269379436545">Groovy and Android at Devoxx</a></li>
<li>The Asciidoctor Java integration <a href="https://twitter.com/ysb33r/status/504665335476539392">(Asciidoctorj) is on the road to Gradle</a></li>
<li><a href="https://twitter.com/cedricchampeau/status/506465145825267712">Marcin Erdmann recently joined the GradleWare</a> team. Congrats to him!</li>
<li>Peter Ledbrook reminds us we can <a href="https://twitter.com/pledbrook/status/505744842568531968">submit talks for the Groovy / Grails eXchange</a> conference in London, next December</li>
</ul>
<h2 id="mailing-lists-posts">Mailing-lists posts</h2>
<ul>
<li>The debate around advocating Groovy vs Java resumes speaking about <a href="http://groovy.329449.n5.nabble.com/Advocating-Groovy-vs-Java-Phase-II-How-to-Manage-Groovy-Adoption-td5720932.html">how to manage adoption</a></li>
<li>A discussion on <a href="http://groovy.329449.n5.nabble.com/Using-groovy-in-a-JEE-project-td5720948.html">using Groovy in a Java EE project</a></li>
<li>Søren Berg Glasius is planning on taking over and <a href="http://groovy.329449.n5.nabble.com/GroovyBlogs-td5721104.html">updating the GroovyBlogs website</a></li>
<li><a href="http://groovy.329449.n5.nabble.com/The-potential-of-Groovy-for-unit-testing-Java-code-The-quot-ticket-to-Groovy-quot-for-millions-of-Ja-td5720984.html">Groovy as a gateway drug into the enterprise</a></li>
<li>Sergei Egorov plays with <a href="http://groovy.329449.n5.nabble.com/Basic-Scala-like-pattern-matching-implementation-td5721078.html">pattern matching in Groovy</a> thanks to his Groovy Macro project</li>
<li>Sergei Egorov created an <a href="http://groovy.329449.n5.nabble.com/Online-Groovy-AST-Console-td5720986.html">online Groovy AST console</a></li>
<li>Recent <a href="http://groovy.329449.n5.nabble.com/Oracle-breaking-Groovy-is-a-problem-td5720928.html">Oracle&rsquo;s Java 8 updates have been breaking Groovy</a> but the Groovy team works with Oracle to resolve those problems</li>
<li>Ways to get an <a href="http://groovy.329449.n5.nabble.com/groovy-in-jar-td5720858.html">executable Groovy program to run without Groovy installed</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Following up a question on the Groovy mailing-list about <a href="https://github.com/glaforge/disable-grab-sample/blob/master/src/main/groovy/disablegrab/DisableGrabTransformation.groovy">how to disable @Grab</a>, Guillaume Laforge created a small sample project showing a global AST transformation that generates compilation errors when the @Grab or @Grapes annotations are used in a Groovy script</li>
<li>A <a href="http://thedailywtf.com/Articles/Securing-Input.aspx">Groovy puzzler in the &ldquo;daily WTF&rdquo;</a> about input validation</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/ben_t_mcguire/status/503631937701830658">First San Diego Groovy user group meeting</a> on September 3rd</li>
<li>The <a href="https://twitter.com/skillsmatter/status/496936164809793536">Groovy &amp; Grails eXchange Call for Paper is already open</a>!</li>
<li>A <a href="https://skillsmatter.com/courses/265-groovy-and-grails-workshop">Groovy and Grails training</a> by Peter Ledbrook in London, end of September</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="http://www.amazon.com/gp/product/1617290963">Grails in Action 2nd edition</a> is now available on Amazon</li>
<li>MrHaki&rsquo;s <a href="http://mrhaki.blogspot.fr/2014/08/grails-goodness-notebook-updated.html">Grails Goodness notebook has been updated</a> with new recipes</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Disable @Grab with a global AST transformation</title><link>https://glaforge.dev/posts/2014/08/07/disable-grab-with-a-global-ast-transformation/</link><pubDate>Thu, 07 Aug 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/08/07/disable-grab-with-a-global-ast-transformation/</guid><description>&lt;p>On the Groovy mailing-list, we had an interesting question about &lt;a href="http://groovy.329449.n5.nabble.com/Disabling-Grape-td5720694.html">how to disable annotations like @Grab&lt;/a>, to prevent users from downloading third-party dependencies. There are a few possibilities for that, but my favorite was to create a global AST transformation that would generate a compilation error if the @Grab annotation is found on an import.&lt;/p>
&lt;p>I created a first &lt;a href="http://groovyconsole.appspot.com/script/5686306919153664">small prototype within a script&lt;/a>, but I used an injected local transformation to get everything working with a simple script. So I decided afterwards to do it for real this time, using a real &lt;a href="https://github.com/glaforge/disable-grab-sample/">project on Github&lt;/a> with a proper global AST transformation this time.&lt;/p></description><content:encoded>
<![CDATA[<p>On the Groovy mailing-list, we had an interesting question about <a href="http://groovy.329449.n5.nabble.com/Disabling-Grape-td5720694.html">how to disable annotations like @Grab</a>, to prevent users from downloading third-party dependencies. There are a few possibilities for that, but my favorite was to create a global AST transformation that would generate a compilation error if the @Grab annotation is found on an import.</p>
<p>I created a first <a href="http://groovyconsole.appspot.com/script/5686306919153664">small prototype within a script</a>, but I used an injected local transformation to get everything working with a simple script. So I decided afterwards to do it for real this time, using a real <a href="https://github.com/glaforge/disable-grab-sample/">project on Github</a> with a proper global AST transformation this time.</p>
<p>If you checkout the project from Github, with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone git@github.com:glaforge/disable-grab-sample.git
</span></span></code></pre></div><p>You then &lsquo;cd&rsquo; in the disable-grab-sample directory, and you can run the following command to launch the Spock test showing the transformation in action:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew <span style="color:#007020">test</span>
</span></span></code></pre></div><p>So what&rsquo;s inside that project? First of all, we need to create our AST transformation (I&rsquo;ll skip the imports for brevity):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#555;font-weight:bold">@GroovyASTTransformation</span><span style="color:#666">(</span>phase <span style="color:#666">=</span> CompilePhase<span style="color:#666">.</span><span style="color:#4070a0">CANONICALIZATION</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">DisableGrabTransformation</span> <span style="color:#007020;font-weight:bold">implements</span> ASTTransformation <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#902000">void</span> <span style="color:#06287e">visit</span><span style="color:#666">(</span>ASTNode<span style="color:#666">[]</span> nodes<span style="color:#666">,</span> SourceUnit source<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#902000">def</span> imports <span style="color:#666">=</span> source<span style="color:#666">.</span><span style="color:#4070a0">AST</span><span style="color:#666">.</span><span style="color:#4070a0">imports</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>imports<span style="color:#666">)</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>            imports<span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> anImport <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>                anImport<span style="color:#666">.</span><span style="color:#4070a0">annotations</span><span style="color:#666">.</span><span style="color:#4070a0">each</span> <span style="color:#666">{</span> anno <span style="color:#666">-&gt;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#007020;font-weight:bold">if</span> <span style="color:#666">(</span>anno<span style="color:#666">.</span><span style="color:#4070a0">classNode</span><span style="color:#666">.</span><span style="color:#4070a0">name</span> <span style="color:#007020;font-weight:bold">in</span> <span style="color:#666">[</span><span style="color:#4070a0">&#39;groovy.lang.Grab&#39;</span><span style="color:#666">,</span> <span style="color:#4070a0">&#39;groovy.lang.Grapes&#39;</span><span style="color:#666">])</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>                        source<span style="color:#666">.</span><span style="color:#4070a0">errorCollector</span><span style="color:#666">.</span><span style="color:#4070a0">addError</span><span style="color:#666">(</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#007020;font-weight:bold">new</span> <span style="color:#06287e">SyntaxErrorMessage</span><span style="color:#666">(</span>
</span></span><span style="display:flex;"><span>                                <span style="color:#007020;font-weight:bold">new</span> <span style="color:#06287e">SyntaxException</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;@Grab and @Grapes are forbidden&#39;</span><span style="color:#666">,</span>
</span></span><span style="display:flex;"><span>                                anImport<span style="color:#666">.</span><span style="color:#4070a0">lineNumber</span><span style="color:#666">,</span> anImport<span style="color:#666">.</span><span style="color:#4070a0">columnNumber</span><span style="color:#666">),</span> 
</span></span><span style="display:flex;"><span>                            source<span style="color:#666">))</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>                <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>We create a class implementing the ASTTransformation class with its visit() method. In that method, we access the &ldquo;module&rdquo; imports, and if there are any import, we iterate over each of them, checking if there are annotations put on them. If those imports are annotated with @Grab or @Grapes (ie. if the annotation class node&rsquo;s fully qualified class name are the FQN of the Grab and Grapes annotations), we then use the error collector to add a new syntax error, so that a compilation error is thrown by the Groovy compiler if ever someone uses @Grab in a Groovy script or class.</p>
<p>We need to wire in that <a href="http://groovy.codehaus.org/Global+AST+Transformations">global transformation</a>. As they are not triggered by annotations like local transformations, we need do declare the transformation in a specific META-INF / services / org.codehaus.groovy.transform.ASTTransformation file, that will just contain one line: the fully qualified class name of the AST transformation that needs to be applied to each script and classes that will be compiled when this transformation is on the classpath. So our services file will just contain:</p>
<pre tabindex="0"><code>disablegrab.DisableGrabTransformation
</code></pre><p>Now we need to see if our transformation is applied, and works as expected. For that purpose, we&rsquo;ll create a <a href="http://spock-framework.readthedocs.org/en/latest/">Spock</a> test:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span><span style="color:#007020;font-weight:bold">class</span> <span style="color:#0e84b5;font-weight:bold">DisableGrabSpec</span> <span style="color:#007020;font-weight:bold">extends</span> Specification <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#902000">def</span> <span style="color:#4070a0">&#34;test&#34;</span><span style="color:#666">()</span> <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>        <span style="color:#002070;font-weight:bold">given:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#902000">def</span> shell <span style="color:#666">=</span> <span style="color:#007020;font-weight:bold">new</span> GroovyShell<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span>        <span style="color:#002070;font-weight:bold">when:</span>
</span></span><span style="display:flex;"><span>        shell<span style="color:#666">.</span><span style="color:#4070a0">evaluate</span> <span style="color:#4070a0">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            @Grab(&#39;org.apache.commons:commons-lang3:3.3.2&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            import org.apache.commons.lang3.StringUtils
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">            println &#34;hi&#34;        
</span></span></span><span style="display:flex;"><span><span style="color:#4070a0">        &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#002070;font-weight:bold">then:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#902000">def</span> e <span style="color:#666">=</span> thrown<span style="color:#666">(</span>MultipleCompilationErrorsException<span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>        e<span style="color:#666">.</span><span style="color:#4070a0">message</span><span style="color:#666">.</span><span style="color:#4070a0">contains</span><span style="color:#666">(</span><span style="color:#4070a0">&#39;@Grab and @Grapes are forbidden&#39;</span><span style="color:#666">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>We are using the GroovyShell class to evaluate (and thus compile) a script that contains a @Grab instruction. When evaluating that script, a compilation error should be thrown, and we indeed assert that it&rsquo;s the case, and that our compilation error contains the message we&rsquo;ve crafted.</p>
<p>Done!</p>
<p>Let&rsquo;s step back a little with a couple words about our build file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>apply <span style="color:#002070;font-weight:bold">plugin:</span> <span style="color:#4070a0">&#39;groovy&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>repositories <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    jcenter<span style="color:#666">()</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>dependencies <span style="color:#666">{</span>
</span></span><span style="display:flex;"><span>    compile <span style="color:#4070a0">&#39;org.codehaus.groovy:groovy-all:2.3.6&#39;</span>
</span></span><span style="display:flex;"><span>    testCompile <span style="color:#4070a0">&#39;org.spockframework:spock-core:0.7-groovy-2.0&#39;</span>
</span></span><span style="display:flex;"><span>    testCompile <span style="color:#4070a0">&#39;org.apache.ivy:ivy:2.4.0-rc1&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Not much to see here actually! We&rsquo;re just applying the groovy plugin, use the <a href="https://bintray.com/bintray/jcenter">jcenter</a> repository from Bintray. We&rsquo;re using Groovy-all, the Spock library for our test scope, as well as the Ivy library that&rsquo;s needed by the grape infrastructure for fully functioning, for retrieving artifacts.</p>
<p>With our Gradle build file, we can call the jar task to create a JAR that will contain the META-INF/services file, and as soon as you&rsquo;ll have that JAR on your classpath with that AST transformation, any script or class compiled with it will get compilation errors if @Grab is used.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #33</title><link>https://glaforge.dev/posts/2014/08/05/groovy-weekly-33/</link><pubDate>Tue, 05 Aug 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/08/05/groovy-weekly-33/</guid><description>&lt;p>You probably remember Groovy and Gradle being selected in RebelLabs’ report as part of the 10 “kick-ass” technologies developers love? But I’m also happy to report that &lt;a href="https://glaforge.dev/posts/2014/08/01/groovy-receives-geek-choice-award/">Groovy won the Geek Choice Award&lt;/a>, showing how groovy Groovy is!&lt;/p>
&lt;p>GR8Conf US 2014 last week is delivering tons of &lt;a href="https://www.youtube.com/channel/UC7wUp2KIa1hoMNn0r7JUVEg">great videos and materials&lt;/a> from all the speakers that came to the conference, and I hope you’ll find time to watch a few. My personal highlight was the great Q&amp;amp;A talk by Scott Hickey and Jim McGill on looking back into &lt;a href="https://www.youtube.com/watch?v=CvpxRoLEq7M">9 years of using Groovy at a big US insurance company&lt;/a>. Don’t miss it!&lt;/p></description><content:encoded>
<![CDATA[<p>You probably remember Groovy and Gradle being selected in RebelLabs’ report as part of the 10 “kick-ass” technologies developers love? But I’m also happy to report that <a href="https://glaforge.dev/posts/2014/08/01/groovy-receives-geek-choice-award/">Groovy won the Geek Choice Award</a>, showing how groovy Groovy is!</p>
<p>GR8Conf US 2014 last week is delivering tons of <a href="https://www.youtube.com/channel/UC7wUp2KIa1hoMNn0r7JUVEg">great videos and materials</a> from all the speakers that came to the conference, and I hope you’ll find time to watch a few. My personal highlight was the great Q&amp;A talk by Scott Hickey and Jim McGill on looking back into <a href="https://www.youtube.com/watch?v=CvpxRoLEq7M">9 years of using Groovy at a big US insurance company</a>. Don’t miss it!</p>
<p>Also, if you missed GR8Conf US 2014, remember there’s <a href="http://springone2gx.com/">SpringOne2GX</a> fast approaching, and the <a href="https://2014.event.springone2gx.com/register">early bird price for registering to the conference ends August 9th</a>, so be quick!</p>
<p>Last but not least, the Groovy Weekly schedule will slow down a little this month, as I’ll take some vacations! So I might be skipping a beat or two in the agenda, but fear not, Groovy Weekly will come back in full force in September!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://grails.org/2.4.3+Release+Notes">Grails 2.4.3</a> is out</li>
<li><a href="http://www.ratpack.io/versions/0.9.7">Ratpack 0.9.7</a> released, with the new Groovy markup template engine, continued work on Reactive Streams, usage of the Gradle Shadow plugin</li>
<li>Renato Athaydes is releasing <a href="https://groups.google.com/forum/#!msg/spockframework/RzXOqfw4OD0/ZoJsL8VTnmgJ">v1.2 of the Spock reports</a> project</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="https://glaforge.dev/posts/2014/08/01/groovy-receives-geek-choice-award/">Groovy wins Geek Choice Award</a>, following up RebelLabs study of the 10 kick-ass technologies developers love</li>
<li>Jacob Aae Mikkelsen published <a href="http://grydeske.net/news/show/55">week 31 of the Grails Diary</a></li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>A tutorial for a <a href="http://wordpress.transentia.com.au/wordpress/2014/07/04/livin-on-the-grid/">REST / grid-driven application with Ratpack, Angular.JS and CoffeeScript</a>, as well as a comparison with Grails</li>
<li>A <a href="http://grooscript.org/ratpack-demo.html">demo using Groovy, Ratpack, the Gradle plugin, and GrooScript</a></li>
<li>Robert Peszek writes that <a href="http://rpeszek.blogspot.fr/2014/08/i-dont-like-hibernate-and-grails-part-1.html">he doesn&rsquo;t like Hibernate and Grails</a> in part one</li>
<li>Robert Peszek continues in part two with the <a href="http://rpeszek.blogspot.fr/2014/08/i-dont-like-hibernategrails-part-2.html">problems he encounters with Hibernate and Grails</a></li>
<li>Getting started guide with <a href="http://app42paas.shephertz.com/dev-center/grails-platform-as-a-service/">Grails on App42 platform as a service</a></li>
<li>Andrés Almiray comes back on <a href="http://www.jroller.com/aalmiray/entry/gradle_glam_jacoco_coveralls_take">Jacoco and Coveralls in his Gradle Glam series</a></li>
<li>MrHaki&rsquo;s Groovy goodness: <a href="http://mrhaki.blogspot.fr/2014/08/groovy-goodness-relax-groovy-will-parse.html">Relax&hellip; Groovy Will Parse Your Wicked JSON</a></li>
<li>MrHaki’s Grails goodness:
<ul>
<li><a href="http://mrhaki.blogspot.fr/2014/08/grails-goodness-use-spring-java.html">use Spring Java configuration</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/08/grails-goodness-using-bintray-jcenter.html">using Bintray JCenter as repository</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/08/grails-goodness-conditionally-load.html">load beans in Java configuration based on Grails environment</a></li>
</ul>
</li>
<li>JaxEnter comes back on the <a href="http://jaxenter.com/gradle-2-0-is-here-sleek-speedy-and-supporting-java-8-50707.html">Gradle 2 release and the interview they made with Guillaume Laforge</a> at the JAX conference</li>
<li><a href="http://www.objectpartners.com/2014/07/29/static-website-generation-in-groovy/">Static site generation with Grain</a> by Mike Hostetler</li>
<li><a href="http://www.oodlestechnologies.com/blogs/Metaobject-Protocol-%26-Interceptors-In-Grails">Meta-Object Protocol and interceptors In Grails</a> by Jasgeet Singh</li>
<li><a href="http://www.intelligrape.com/blog/2014/08/04/grails-unit-test-filters-with-the-injected-service/">Grails unit test filters with the injected service</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Julien Viet on <a href="http://www.youtube.com/watch?v=ZRQwZfP2uIc&amp;feature=youtu.be&amp;a">CRaSH</a>, the shell for the Java Virtual Machine, recorded at GR8Conf Europe 2014</li>
<li>GR8Conf US 2014 specials
<ul>
<li>The <a href="https://twitter.com/gr8confus/status/494856150261567489">GR8Conf US 2014 videos</a> will appear progressively on YouTube and the slides on Github</li>
<li>A look back at the <a href="https://www.youtube.com/watch?v=CvpxRoLEq7M">9 years of Groovy at the Mutual of Omaha insurance company</a> by Scott Hickey and Jim McGill</li>
<li>The now famous <a href="http://fr.slideshare.net/GroovyPuzzlers/the-groovy-puzzlers-gr8-conf-us-2014">Groovy Puzzlers</a> talk by Baruch Sadogursky and Andrés Almiray</li>
<li>Guillaume Laforge published his slide deck about &ldquo;<a href="https://speakerdeck.com/glaforge/groovy-in-2014-and-beyond-at-gr8conf-us-2014">Groovy in 2014 and beyond</a>&rdquo;</li>
<li>The <a href="https://www.youtube.com/watch?v=A17geQa5WQc">Grails asset pipeline</a> by Ted Naleid</li>
<li><a href="https://www.youtube.com/watch?v=fhGRLFLamN8">Groovy Vampires: Groovy, REST, NoSQL</a> and Bad Marketing by Ken Kousen</li>
<li><a href="https://www.youtube.com/watch?v=wF8gjfh0Kyw">Experiences using Grails in a micro-service architecture</a> by Jeff Beck</li>
<li><a href="https://www.youtube.com/watch?v=_cdb7zYNEFg&amp;feature=youtu.be">Hibernate metrics: paying attention to the ORM behind the curtain</a> by David Kuster</li>
<li><a href="https://www.youtube.com/watch?v=i28F13zZwlg&amp;feature=youtu.be">Spock, a logical framework for enterprise testing</a> by Ken Kousen</li>
<li><a href="https://www.youtube.com/watch?v=VvTxOwYsj88">Functional testing your Grails app with Geb</a> by Collin Harrington</li>
<li><a href="https://www.youtube.com/watch?v=-WQGIZ90H2w&amp;list=UU7wUp2KIa1hoMNn0r7JUVEg">Vagrant</a> by Kyle Boon at GR8Conf US 2014</li>
<li><a href="https://www.youtube.com/watch?v=8W8F9zVmwDs">Introduction to graph databases and Neo4J</a> by Stefan Armbruster</li>
<li><a href="https://www.youtube.com/watch?v=KFz0n7NLXJA">Polyglot web programming with Grails</a> by Jeff Brown</li>
<li><a href="https://www.youtube.com/watch?v=ObbQw6_VbWs">Angular.JS for the Grails enthusiast</a> by Will Buck</li>
<li><a href="https://www.youtube.com/watch?v=IGgsARk8BGE&amp;feature=youtu.be">Fine-grained security in Grails</a> presented by Andrew Miller</li>
</ul>
</li>
</ul>
<h2 id="google-posts">Google+ posts</h2>
<ul>
<li>The Groovy <a href="https://plus.google.com/u/0/100991326727521012956/posts/BzRTzUjRinF?cfem=1">markup template engine used as an alternative Magnolia CMS renderer</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li><a href="https://twitter.com/gvmtool/status/494515638669705216">GVM has passed 60,000 unique installs</a>!</li>
<li>Backward and binary compatibility is an important concern for the Groovy project, and Cédric Champeau just developed a <a href="https://twitter.com/cedricchampeau/status/494457760986456064">Gradle plugin integrated in the Groovy build to create reports of binary compatibility</a> between Groovy versions</li>
<li><a href="https://twitter.com/gvmtool/status/494482701567528960">Grails 2.4.3</a> available through GVM</li>
<li><a href="https://twitter.com/gvmtool/status/494827442641833984">Groovy 2.4.0-beta-2</a> available through GVM</li>
<li>The <a href="https://twitter.com/ratpackweb/status/494957092474605570">Ratpack project publishes its test coverage</a> and ask for help for improving it</li>
<li>David Norton asks the question: <a href="https://twitter.com/davidnortonjr/status/494197093524054017">how many actuaries in the world can put Groovy on their résumés</a>?</li>
<li><a href="https://twitter.com/ratpackweb/status/494985575863050240">Ratpack reminds us its got a forum</a> for user support</li>
<li>Senthil Gopal really <a href="https://twitter.com/sengopal/status/495593355032555521">enjoyed the Groovy puzzlers</a> by Baruch Sadogursky and Andrés Almiray at GR8Conf US 2014</li>
<li>The <a href="https://twitter.com/javafxpert/status/495921622730018816">JavaFX FXPlayground has added support for Groovy</a></li>
<li>Andrés Almiray remarks the <a href="https://twitter.com/aalmiray/status/496391849356300289">ongoing growth of Griffon plugins</a></li>
<li>Robert Guerra is <a href="https://twitter.com/robertoguerra19/status/496309476933517312">having fun with Ratpack and ElasticSearch</a></li>
<li>Discussing with Robert Fletcher reminded us of <a href="https://twitter.com/rfletcherew/status/496636379485728768">Groovy Koans</a>, for getting acquainted with Groovy</li>
<li>The <a href="https://twitter.com/lspacewalker/status/496640143097626624">Ratpack.io website is powered by the Groovy markup template engine</a></li>
<li>Andrés Almiray is using the <a href="https://twitter.com/aalmiray/status/496759565309186048">Gradle Git plugin</a> to easily push content to Github&rsquo;s pages</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Laurent Huet shows how to get the <a href="https://gist.github.com/lhuet/9cc05bab4417c0f1026f">list of twitter followers with Groovy and the Twitter4J</a> library</li>
<li>Jack Holt created a <a href="https://bitbucket.org/jackcholt/spring4hw">Spring MVC 4 demo using Groovy</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://2014.event.springone2gx.com/register">Early bird for SpringOne2GX 2014 ends August 9th</a>, be quick!</li>
<li><a href="http://www.meetup.com/Bangalore-Groovy-Grails-Meetup/">Bagalore Groovy meetup</a> next saturday</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy receives Geek Choice Award</title><link>https://glaforge.dev/posts/2014/08/01/groovy-receives-geek-choice-award/</link><pubDate>Fri, 01 Aug 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/08/01/groovy-receives-geek-choice-award/</guid><description>&lt;p>&lt;figure>
&lt;a href="#img-d479ec62a6d151ecb89ce8c5a9963c3f">
&lt;img src="https://glaforge.dev/img/misc/Geek-Choice-Awards-JVM-Language-300x300-black.png"
alt=""
/>
&lt;/a>
&lt;figcaption>&lt;/figcaption>
&lt;/figure>
&lt;div class="lightbox" id="img-d479ec62a6d151ecb89ce8c5a9963c3f">
&lt;a href="#_" class="lightbox-overlay">&lt;/a>
&lt;img src="https://glaforge.dev/img/misc/Geek-Choice-Awards-JVM-Language-300x300-black.png"
alt=""
/>
&lt;div class="lightbox-caption">&lt;/div>
&lt;/div>
&lt;/p>
&lt;p>&lt;a href="http://zeroturnaround.com/rebellabs/">RebelLabs&lt;/a> launched an annual study and awards to select 10 &amp;ldquo;kick-ass&amp;rdquo; technologies that developers love, and both Gradle and Groovy were among the winners!&lt;/p>
&lt;p>You can learn more about this &lt;a href="http://pages.zeroturnaround.com/Kickass-Technologies.html">&amp;ldquo;kick-ass&amp;rdquo; technology study&lt;/a> in the PDF report they crafted and which can be downloaded there (form filing required to download the PDF).&lt;/p>
&lt;p>Details are available for:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://zeroturnaround.com/rebellabs/10-kick-ass-technologies-modern-developers-love/4/">Groovy&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://zeroturnaround.com/rebellabs/10-kick-ass-technologies-modern-developers-love/3/">Gradle&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>It&amp;rsquo;s always nice to see such reports and awards recognizing the great work and energy the Groovy and Gradle teams have been put into developing those technologies, and making developers around the world delighted to work with them.&lt;/p></description><content:encoded>
<![CDATA[<p><figure>
  <a href="#img-d479ec62a6d151ecb89ce8c5a9963c3f">
    <img src="/img/misc/Geek-Choice-Awards-JVM-Language-300x300-black.png"
      alt=""
       />
  </a>
  <figcaption></figcaption>
</figure>
<div class="lightbox" id="img-d479ec62a6d151ecb89ce8c5a9963c3f">
  <a href="#_" class="lightbox-overlay"></a>
  <img src="/img/misc/Geek-Choice-Awards-JVM-Language-300x300-black.png"
    alt=""
     />
  <div class="lightbox-caption"></div>
</div>
</p>
<p><a href="http://zeroturnaround.com/rebellabs/">RebelLabs</a> launched an annual study and awards to select 10 &ldquo;kick-ass&rdquo; technologies that developers love, and both Gradle and Groovy were among the winners!</p>
<p>You can learn more about this <a href="http://pages.zeroturnaround.com/Kickass-Technologies.html">&ldquo;kick-ass&rdquo; technology study</a> in the PDF report they crafted and which can be downloaded there (form filing required to download the PDF).</p>
<p>Details are available for:</p>
<ul>
<li><a href="http://zeroturnaround.com/rebellabs/10-kick-ass-technologies-modern-developers-love/4/">Groovy</a></li>
<li><a href="http://zeroturnaround.com/rebellabs/10-kick-ass-technologies-modern-developers-love/3/">Gradle</a></li>
</ul>
<p>It&rsquo;s always nice to see such reports and awards recognizing the great work and energy the Groovy and Gradle teams have been put into developing those technologies, and making developers around the world delighted to work with them.</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #32</title><link>https://glaforge.dev/posts/2014/07/30/groovy-weekly-32/</link><pubDate>Wed, 30 Jul 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/30/groovy-weekly-32/</guid><description>&lt;p>Live from GR8Conf US 2014, in Minneapolis, MN, USA! The code samples and slides of presentations are already flowing and are listed below in the presentations section. Lots of great content and talks to learn from.&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>Andrés Almiray &lt;a href="http://griffon-user.3225736.n2.nabble.com/ANN-Griffon-2-0-0-RC1-released-td7578844.html">released Griffon 2.0.0-rc-1&lt;/a> live on stage at GR8Conf US 2014, along with the announcement of the &lt;a href="http://new.griffon-framework.org/">new Griffon website&lt;/a>&lt;/li>
&lt;li>Cédric Champeau announces the &lt;a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-2-3-5-released-and-upward-compatibility-td5720557.html#a5720571">releases of Groovy 2.3.6 and Groovy 2.4.0-beta-2&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge announced &lt;a href="https://glaforge.dev/posts/2014/07/25/groovy-2-3-5-out-with-upward-compatibility/">Groovy 2.3.5 with upward compatibility&lt;/a>&lt;/li>
&lt;li>Cédric Champeau released &lt;a href="https://twitter.com/cedricchampeau/status/492594605003902976">v0.3.0 of the Gradle Groovy Android plugin&lt;/a>, allowing you to support Android libraries&lt;/li>
&lt;li>Cédric Champeau announces &lt;a href="https://twitter.com/cedricchampeau/status/493493646940184576">v0.1.2 of the JMH Gradle plugin&lt;/a>&lt;/li>
&lt;li>Kunal Dabir released &lt;a href="http://glide-gae.appspot.com">Glide 0.3.3&lt;/a>, with upgraded dependencies, and more goodness for Windows&lt;/li>
&lt;li>Cédric Champeau releases a &lt;a href="https://github.com/melix/japicmp-gradle-plugin">Japicmp Gradle plugin&lt;/a> to compare binary compatibility between JAR binaries&lt;/li>
&lt;li>&lt;a href="https://twitter.com/johnrengelman/status/494228881155424256">Gradle Shadow plugin 1.0.3&lt;/a> released&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://www.elasticsearch.org/blog/elasticsearch-1-3-0-released/">ElasticSearch using Groovy scripting by default&lt;/a>&lt;/li>
&lt;li>The &lt;a href="http://new.griffon-framework.org/index.html">new Griffon website&lt;/a> is online!&lt;/li>
&lt;li>Dan Woods is contributing an &lt;a href="https://github.com/ratpack/ratpack/pull/395">Apache Camel module for Ratpack&lt;/a>&lt;/li>
&lt;li>Jacob Aae Mikkelsen published &lt;a href="http://grydeske.net/news/show/54">Grails Diary&lt;/a>, week 30&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Ryan Vanderwerf on &lt;a href="http://rvanderwerf.blogspot.com/2014/07/lego-mindstorms-ev3-lejos-and-groovy.html">getting started with programming with Groovy on Lego Mindstorms&lt;/a> EV3&lt;/li>
&lt;li>Andrés Almiray&amp;rsquo;s &lt;a href="http://www.jroller.com/aalmiray/entry/gradle_glam_jacoco_coveralls">Gradle Glam: JaCoCo + CoverAlls&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://solidsoft.wordpress.com/2014/06/05/null-object-pattern-implementation-in-groovy-a-map-with-default-behavior/">NullObject pattern implementation&lt;/a> in Groovy, a map with default behavior, by Marcin Zajączkowski&lt;/li>
&lt;li>John Ferguson Smart wrote about data-driven testing with JUnit and highlighting &lt;a href="https://weblogs.java.net/blog/johnsmart/archive/2014/07/23/data-driven-unit-testing-java">Spock&amp;rsquo;s data-driven approach&lt;/a>&lt;/li>
&lt;li>Guide showing how to use &lt;a href="http://grooscript.org/grails-plugin/rest-demo.html">Grails&amp;rsquo; REST support from GSPs using GrooScript&lt;/a>&lt;/li>
&lt;li>Craig Burke writes about &lt;a href="http://www.craigburke.com/2014/07/24/angular-grails-asset-pipeline.html">Grails, Angular.JS and the Asset pipeline&lt;/a>&lt;/li>
&lt;li>Andrés Almiray shows how to &lt;a href="http://www.jroller.com/aalmiray/entry/how_to_test_javafx_services1">test JavaFX services, the Griffon way&lt;/a>&lt;/li>
&lt;li>Juan Vazquez wrote about adding map / filter / reduce &lt;a href="http://javazquez.com/juan/2013/02/05/add-map-reduce-and-filter-to-groovy-with-groovy-extension-modules/">method aliases with a Groovy extension module&lt;/a>&lt;/li>
&lt;li>André Steingreß explained how to create your own &lt;a href="http://blog.andresteingress.com/2013/01/25/groovy-2-1-type-checking-extensions/">Groovy type checking extensions&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://grooscript.org/grails-plugin/websocket-support.html">WebSocket support with GrooScript within Grails&lt;/a> applications&lt;/li>
&lt;li>&lt;a href="http://www.intelligrape.com/blog/2014/07/24/email-tracking-using-mailgun-with-grails-application/">Email tracking with MailGun in a Grails&lt;/a> application&lt;/li>
&lt;li>&lt;a href="http://www.intelligrape.com/blog/2014/07/24/jsoup-for-the-grails-soul/">Jsoup for the Grails soul&lt;/a>&lt;/li>
&lt;li>A &lt;a href="http://rominirani.com/2014/07/28/gradle-tutorial-series-an-overview/">Gradle tutorial series&lt;/a>&lt;/li>
&lt;li>Gradle Tutorial: Part 1 — &lt;a href="http://rominirani.com/2014/07/28/gradle-tutorial-part-1-installation-setup/">Installation + Setup&lt;/a>&lt;/li>
&lt;li>Gradle Tutorial: Part 2 — &lt;a href="http://rominirani.com/2014/07/28/gradle-tutorial-part-2-java-projects/">Java Projects&lt;/a>&lt;/li>
&lt;li>Gradle Tutorial: Part 3 — &lt;a href="http://rominirani.com/2014/07/29/gradle-tutorial-part-3-multiple-java-projects/">Multiple Java Projects&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.infoq.com/news/2014/07/elasticsearch-1-3-0">InfoQ on ElasticSearch using Groovy for scripting&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="presentations">Presentations&lt;/h2>
&lt;ul>
&lt;li>Cédric Champeau demonstrates the &lt;a href="http://www.youtube.com/watch?v=AebkFsfcuDg&amp;amp;feature=youtu.be&amp;amp;a">Groovy support for developing Android applications&lt;/a>&lt;/li>
&lt;li>Jeff Beck published his GR8Conf US 2014 &lt;a href="https://github.com/beckje01/ratpack-subscription-api">Ratpack intro presentation and code&lt;/a>&lt;/li>
&lt;li>Colin Harrington published his GR8Conf US 2014 presentation slides on &lt;a href="http://slides.com/colinharrington/how-to-test-your-grails-application#/">how to test your Grails application&lt;/a>&lt;/li>
&lt;li>Colin Harrington on &lt;a href="http://slides.com/colinharrington/functional-testing-your-grails-app-with-geb--2#/">functional testing your Grails apps with Geb&lt;/a>, at GR8Conf US 2014&lt;/li>
&lt;li>Ryan Vanderwerf on &lt;a href="https://twitter.com/ryanvanderwerf/status/493895466480717825">getting Grails / Groovy into the enterprise&lt;/a>, at GR8Conf US 2014&lt;/li>
&lt;li>Ted Naleid on us&lt;a href="https://speakerdeck.com/tednaleid/using-grails-asset-pipeline-plugin">ing the Grails Asset pipeline&lt;/a>, presented at GR8Conf US 2014&lt;/li>
&lt;li>&lt;a href="http://www.slideshare.net/lhotari/performance-tuning-grails-applications-applications-gr8-confus">Performance tuning Grails applications&lt;/a> by Lari Hotari at GR8Conf US 2014&lt;/li>
&lt;li>&lt;a href="https://github.com/onetribeyoyo/fine-grained-security">Fine-grained security&lt;/a> by Andy Miller at GR8Conf US 2014&lt;/li>
&lt;li>&lt;a href="http://fr.slideshare.net/lhotari/ratpack-and-grails-3-gr8confus">Grails 3 and Ratpack, Micro-services&lt;/a>, and more, by Lari Hotari, at GR8Conf US 2014&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gr8confus/status/494255220654764032">GR8Conf US 2014 slides and samples&lt;/a> are being collected on Github&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-list-posts">Mailing-list posts&lt;/h2>
&lt;ul>
&lt;li>Interesting thread on the Groovy user list about &lt;a href="http://groovy.329449.n5.nabble.com/Advocating-Groovy-vs-Java-td5720573.html">Groovy performance, advocating Groovy vs Java&lt;/a>, and more&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>How &lt;a href="https://twitter.com/targetcareers/status/494156672420831232">Target is embracing Groovy&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/highlightjs/status/492421799410282496">Highlight.js now supports Groovy syntax highlighting&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge contributed a &lt;a href="https://twitter.com/glaforge/status/492206420017049600">Groovy logo for the devicons font&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/492244790101426176">Vert.x 2.1.2&lt;/a> is available through GVM&lt;/li>
&lt;li>Angel Ruiz likes Java 8 streams but &lt;a href="https://twitter.com/aruizca/status/492551332218159104">loves Tim Yates&amp;rsquo; Groovy streams&lt;/a> even more&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/493004971810762752">Glide 0.3.3&lt;/a> available through GVM&lt;/li>
&lt;li>&lt;a href="https://twitter.com/gvmtool/status/493042458318864384">Groovy 2.3.5&lt;/a> available through GVM&lt;/li>
&lt;li>Jeff Beck suggests to &lt;a href="https://twitter.com/sheetsj/status/493841650217545728">use GVM and Lazybones for getting started with Ratpack&lt;/a>&lt;/li>
&lt;li>A new Groovy-related account to follow on Twitter with &lt;a href="https://twitter.com/groovypuzzlers/status/493821368157220865">Groovy puzzlers&lt;/a>&lt;/li>
&lt;li>Ken Kousen showed the nice &lt;a href="https://twitter.com/rfletcherew/status/494138116043997184">Grails url-mapping-reports command&lt;/a> on stage&lt;/li>
&lt;li>Rob Fletcher claims &lt;a href="https://twitter.com/elanorriley/status/494205248500555778">Ratpack is your gateway drug to non-blocking&lt;/a>&lt;/li>
&lt;li>Thanks to the GR8Ladies work, we&amp;rsquo;re seeing &lt;a href="https://twitter.com/wmacgyver/status/494234801348812801">more women participating to GR8Conf&lt;/a>&lt;/li>
&lt;li>Scott Hickey is still &lt;a href="https://twitter.com/jshickey/status/494263654166982657">impressed, amazed and appreciative of the Groovy community&lt;/a> after 9 years of using Groovy in various enterprise projects&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Andrés Almiray shows how to &lt;a href="https://gist.github.com/aalmiray/e6f54aa4b3803be0bcac">aggregate JaCoCo reports in a multi-project Gradle build&lt;/a>&lt;/li>
&lt;li>Luke Daley adds &lt;a href="https://github.com/ratpack/ratpack/blob/master/ratpack-test/src/test/groovy/ratpack/test/exec/ExecHarnessSpec.groovy#L27">test support for async support in Ratpack&lt;/a>&lt;/li>
&lt;li>Andrey Hihlovskiy shows a snippet &lt;a href="https://twitter.com/andreyhihlovski/status/492930646252457984">removing parts of a string with regular expressions&lt;/a>&lt;/li>
&lt;li>Cédric Champeau shows an example &lt;a href="https://twitter.com/cedricchampeau/status/492611846953005056">usage of the Groovy Android Gradle plugin with an Android library&lt;/a>&lt;/li>
&lt;li>MrHaki’s GR8Conf US 2014 G* Goodness talks’ samples:
&lt;ul>
&lt;li>&lt;a href="https://github.com/mrhaki/gr8conf2014us-gradle-goodness">code samples of his Gradle Goodness presentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mrhaki/gr8conf2014us-grails-goodness">code samples of his Grails Goodness presentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mrhaki/gr8conf2014us-groovy-goodness">code samples of his Groovy Goodness presentation&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Erik Pragt shares a snippet of code showing &lt;a href="https://gist.github.com/bodiam/2ab1b98a1abb33f5ba1b">exceptions implementing a no-stacktrace trait to make exceptions fast and cheap&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/ManningBooks/status/492749460603432960">Grails in Action, 2nd edition, is available in epub and kindle formats&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="events">Events&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://2014.event.springone2gx.com/register">Early bird for SpringOne2GX is ending on August 9th&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="jobs">Jobs&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/danveloper/status/494214232343203840">Target is hiring Groovy developers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>Live from GR8Conf US 2014, in Minneapolis, MN, USA! The code samples and slides of presentations are already flowing and are listed below in the presentations section. Lots of great content and talks to learn from.</p>
<h2 id="releases">Releases</h2>
<ul>
<li>Andrés Almiray <a href="http://griffon-user.3225736.n2.nabble.com/ANN-Griffon-2-0-0-RC1-released-td7578844.html">released Griffon 2.0.0-rc-1</a> live on stage at GR8Conf US 2014, along with the announcement of the <a href="http://new.griffon-framework.org/">new Griffon website</a></li>
<li>Cédric Champeau announces the <a href="http://groovy.329449.n5.nabble.com/ANN-Groovy-2-3-5-released-and-upward-compatibility-td5720557.html#a5720571">releases of Groovy 2.3.6 and Groovy 2.4.0-beta-2</a></li>
<li>Guillaume Laforge announced <a href="https://glaforge.dev/posts/2014/07/25/groovy-2-3-5-out-with-upward-compatibility/">Groovy 2.3.5 with upward compatibility</a></li>
<li>Cédric Champeau released <a href="https://twitter.com/cedricchampeau/status/492594605003902976">v0.3.0 of the Gradle Groovy Android plugin</a>, allowing you to support Android libraries</li>
<li>Cédric Champeau announces <a href="https://twitter.com/cedricchampeau/status/493493646940184576">v0.1.2 of the JMH Gradle plugin</a></li>
<li>Kunal Dabir released <a href="http://glide-gae.appspot.com">Glide 0.3.3</a>, with upgraded dependencies, and more goodness for Windows</li>
<li>Cédric Champeau releases a <a href="https://github.com/melix/japicmp-gradle-plugin">Japicmp Gradle plugin</a> to compare binary compatibility between JAR binaries</li>
<li><a href="https://twitter.com/johnrengelman/status/494228881155424256">Gradle Shadow plugin 1.0.3</a> released</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://www.elasticsearch.org/blog/elasticsearch-1-3-0-released/">ElasticSearch using Groovy scripting by default</a></li>
<li>The <a href="http://new.griffon-framework.org/index.html">new Griffon website</a> is online!</li>
<li>Dan Woods is contributing an <a href="https://github.com/ratpack/ratpack/pull/395">Apache Camel module for Ratpack</a></li>
<li>Jacob Aae Mikkelsen published <a href="http://grydeske.net/news/show/54">Grails Diary</a>, week 30</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Ryan Vanderwerf on <a href="http://rvanderwerf.blogspot.com/2014/07/lego-mindstorms-ev3-lejos-and-groovy.html">getting started with programming with Groovy on Lego Mindstorms</a> EV3</li>
<li>Andrés Almiray&rsquo;s <a href="http://www.jroller.com/aalmiray/entry/gradle_glam_jacoco_coveralls">Gradle Glam: JaCoCo + CoverAlls</a></li>
<li><a href="http://solidsoft.wordpress.com/2014/06/05/null-object-pattern-implementation-in-groovy-a-map-with-default-behavior/">NullObject pattern implementation</a> in Groovy, a map with default behavior, by Marcin Zajączkowski</li>
<li>John Ferguson Smart wrote about data-driven testing with JUnit and highlighting <a href="https://weblogs.java.net/blog/johnsmart/archive/2014/07/23/data-driven-unit-testing-java">Spock&rsquo;s data-driven approach</a></li>
<li>Guide showing how to use <a href="http://grooscript.org/grails-plugin/rest-demo.html">Grails&rsquo; REST support from GSPs using GrooScript</a></li>
<li>Craig Burke writes about <a href="http://www.craigburke.com/2014/07/24/angular-grails-asset-pipeline.html">Grails, Angular.JS and the Asset pipeline</a></li>
<li>Andrés Almiray shows how to <a href="http://www.jroller.com/aalmiray/entry/how_to_test_javafx_services1">test JavaFX services, the Griffon way</a></li>
<li>Juan Vazquez wrote about adding map / filter / reduce <a href="http://javazquez.com/juan/2013/02/05/add-map-reduce-and-filter-to-groovy-with-groovy-extension-modules/">method aliases with a Groovy extension module</a></li>
<li>André Steingreß explained how to create your own <a href="http://blog.andresteingress.com/2013/01/25/groovy-2-1-type-checking-extensions/">Groovy type checking extensions</a></li>
<li><a href="http://grooscript.org/grails-plugin/websocket-support.html">WebSocket support with GrooScript within Grails</a> applications</li>
<li><a href="http://www.intelligrape.com/blog/2014/07/24/email-tracking-using-mailgun-with-grails-application/">Email tracking with MailGun in a Grails</a> application</li>
<li><a href="http://www.intelligrape.com/blog/2014/07/24/jsoup-for-the-grails-soul/">Jsoup for the Grails soul</a></li>
<li>A <a href="http://rominirani.com/2014/07/28/gradle-tutorial-series-an-overview/">Gradle tutorial series</a></li>
<li>Gradle Tutorial: Part 1 — <a href="http://rominirani.com/2014/07/28/gradle-tutorial-part-1-installation-setup/">Installation + Setup</a></li>
<li>Gradle Tutorial: Part 2 — <a href="http://rominirani.com/2014/07/28/gradle-tutorial-part-2-java-projects/">Java Projects</a></li>
<li>Gradle Tutorial: Part 3 — <a href="http://rominirani.com/2014/07/29/gradle-tutorial-part-3-multiple-java-projects/">Multiple Java Projects</a></li>
<li><a href="http://www.infoq.com/news/2014/07/elasticsearch-1-3-0">InfoQ on ElasticSearch using Groovy for scripting</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Cédric Champeau demonstrates the <a href="http://www.youtube.com/watch?v=AebkFsfcuDg&amp;feature=youtu.be&amp;a">Groovy support for developing Android applications</a></li>
<li>Jeff Beck published his GR8Conf US 2014 <a href="https://github.com/beckje01/ratpack-subscription-api">Ratpack intro presentation and code</a></li>
<li>Colin Harrington published his GR8Conf US 2014 presentation slides on <a href="http://slides.com/colinharrington/how-to-test-your-grails-application#/">how to test your Grails application</a></li>
<li>Colin Harrington on <a href="http://slides.com/colinharrington/functional-testing-your-grails-app-with-geb--2#/">functional testing your Grails apps with Geb</a>, at GR8Conf US 2014</li>
<li>Ryan Vanderwerf on <a href="https://twitter.com/ryanvanderwerf/status/493895466480717825">getting Grails / Groovy into the enterprise</a>, at GR8Conf US 2014</li>
<li>Ted Naleid on us<a href="https://speakerdeck.com/tednaleid/using-grails-asset-pipeline-plugin">ing the Grails Asset pipeline</a>, presented at GR8Conf US 2014</li>
<li><a href="http://www.slideshare.net/lhotari/performance-tuning-grails-applications-applications-gr8-confus">Performance tuning Grails applications</a> by Lari Hotari at GR8Conf US 2014</li>
<li><a href="https://github.com/onetribeyoyo/fine-grained-security">Fine-grained security</a> by Andy Miller at GR8Conf US 2014</li>
<li><a href="http://fr.slideshare.net/lhotari/ratpack-and-grails-3-gr8confus">Grails 3 and Ratpack, Micro-services</a>, and more, by Lari Hotari, at GR8Conf US 2014</li>
<li><a href="https://twitter.com/gr8confus/status/494255220654764032">GR8Conf US 2014 slides and samples</a> are being collected on Github</li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>Interesting thread on the Groovy user list about <a href="http://groovy.329449.n5.nabble.com/Advocating-Groovy-vs-Java-td5720573.html">Groovy performance, advocating Groovy vs Java</a>, and more</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>How <a href="https://twitter.com/targetcareers/status/494156672420831232">Target is embracing Groovy</a></li>
<li><a href="https://twitter.com/highlightjs/status/492421799410282496">Highlight.js now supports Groovy syntax highlighting</a></li>
<li>Guillaume Laforge contributed a <a href="https://twitter.com/glaforge/status/492206420017049600">Groovy logo for the devicons font</a></li>
<li><a href="https://twitter.com/gvmtool/status/492244790101426176">Vert.x 2.1.2</a> is available through GVM</li>
<li>Angel Ruiz likes Java 8 streams but <a href="https://twitter.com/aruizca/status/492551332218159104">loves Tim Yates&rsquo; Groovy streams</a> even more</li>
<li><a href="https://twitter.com/gvmtool/status/493004971810762752">Glide 0.3.3</a> available through GVM</li>
<li><a href="https://twitter.com/gvmtool/status/493042458318864384">Groovy 2.3.5</a> available through GVM</li>
<li>Jeff Beck suggests to <a href="https://twitter.com/sheetsj/status/493841650217545728">use GVM and Lazybones for getting started with Ratpack</a></li>
<li>A new Groovy-related account to follow on Twitter with <a href="https://twitter.com/groovypuzzlers/status/493821368157220865">Groovy puzzlers</a></li>
<li>Ken Kousen showed the nice <a href="https://twitter.com/rfletcherew/status/494138116043997184">Grails url-mapping-reports command</a> on stage</li>
<li>Rob Fletcher claims <a href="https://twitter.com/elanorriley/status/494205248500555778">Ratpack is your gateway drug to non-blocking</a></li>
<li>Thanks to the GR8Ladies work, we&rsquo;re seeing <a href="https://twitter.com/wmacgyver/status/494234801348812801">more women participating to GR8Conf</a></li>
<li>Scott Hickey is still <a href="https://twitter.com/jshickey/status/494263654166982657">impressed, amazed and appreciative of the Groovy community</a> after 9 years of using Groovy in various enterprise projects</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Andrés Almiray shows how to <a href="https://gist.github.com/aalmiray/e6f54aa4b3803be0bcac">aggregate JaCoCo reports in a multi-project Gradle build</a></li>
<li>Luke Daley adds <a href="https://github.com/ratpack/ratpack/blob/master/ratpack-test/src/test/groovy/ratpack/test/exec/ExecHarnessSpec.groovy#L27">test support for async support in Ratpack</a></li>
<li>Andrey Hihlovskiy shows a snippet <a href="https://twitter.com/andreyhihlovski/status/492930646252457984">removing parts of a string with regular expressions</a></li>
<li>Cédric Champeau shows an example <a href="https://twitter.com/cedricchampeau/status/492611846953005056">usage of the Groovy Android Gradle plugin with an Android library</a></li>
<li>MrHaki’s GR8Conf US 2014 G* Goodness talks’ samples:
<ul>
<li><a href="https://github.com/mrhaki/gr8conf2014us-gradle-goodness">code samples of his Gradle Goodness presentation</a></li>
<li><a href="https://github.com/mrhaki/gr8conf2014us-grails-goodness">code samples of his Grails Goodness presentation</a></li>
<li><a href="https://github.com/mrhaki/gr8conf2014us-groovy-goodness">code samples of his Groovy Goodness presentation</a></li>
</ul>
</li>
<li>Erik Pragt shares a snippet of code showing <a href="https://gist.github.com/bodiam/2ab1b98a1abb33f5ba1b">exceptions implementing a no-stacktrace trait to make exceptions fast and cheap</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/ManningBooks/status/492749460603432960">Grails in Action, 2nd edition, is available in epub and kindle formats</a></li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://2014.event.springone2gx.com/register">Early bird for SpringOne2GX is ending on August 9th</a></li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li><a href="https://twitter.com/danveloper/status/494214232343203840">Target is hiring Groovy developers</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.3.5 out with upward compatibility</title><link>https://glaforge.dev/posts/2014/07/25/groovy-2-3-5-out-with-upward-compatibility/</link><pubDate>Fri, 25 Jul 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/25/groovy-2-3-5-out-with-upward-compatibility/</guid><description>&lt;p>The Groovy team is pleased to announce the release of Groovy 2.3.5.&lt;/p>
&lt;p>Groovy 2.3.5 is a bug fix release of our Groovy 2.3 branch.&lt;/p>
&lt;p>You’ll find fixes for static compilation and type-checking, JSON serialization issues, markup template engine errors, and performance improvements.&lt;/p>
&lt;p>We care a lot about backward and binary compatibility, but in this release, we also thought about upward compatibility, so that code compiled with a newer version can even run on an older runtime.&lt;/p></description><content:encoded>
<![CDATA[<p>The Groovy team is pleased to announce the release of Groovy 2.3.5.</p>
<p>Groovy 2.3.5 is a bug fix release of our Groovy 2.3 branch.</p>
<p>You’ll find fixes for static compilation and type-checking, JSON serialization issues, markup template engine errors, and performance improvements.</p>
<p>We care a lot about backward and binary compatibility, but in this release, we also thought about upward compatibility, so that code compiled with a newer version can even run on an older runtime.</p>
<p>So we leveraged this version to add a new artifact, named groovy-backports-compat23. This artifact shouldn’t be necessary for most of you, but if you face an error like:</p>
<pre tabindex="0"><code>Caused by: java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.typehandling.ShortTypeHandling
  at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
</code></pre><p>in your project, then it means that a class has been compiled with Groovy 2.3+ but that you are trying to use it with an older version of Groovy. By adding this jar on classpath, you give a chance to your program to run. This may be particularily interesting for Gradle users that want to use a plugin built on Gradle 2+ on older versions of Gradle and face this error. Adding the following line to their build files should help:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-groovy" data-lang="groovy"><span style="display:flex;"><span>buildscript <span style="color:#666">{</span>  
</span></span><span style="display:flex;"><span>    <span style="color:#60a0b0;font-style:italic">// ...  
</span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"></span>    dependencies <span style="color:#666">{</span>  
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#39;some plugin build on gradle 2&#39;</span>  
</span></span><span style="display:flex;"><span>        classpath <span style="color:#4070a0">&#39;org.codehaus.groovy:groovy-backports-compat23:2.3.5&#39;</span>  
</span></span><span style="display:flex;"><span>    <span style="color:#666">}</span> 
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
</span></span></code></pre></div><p>Note that for now, this jar only contains the ShortTypeHandlingClass. Future versions may include more.</p>
<p>You can download Groovy 2.3.5 here:
<a href="http://beta.groovy-lang.org/download.html">http://beta.groovy-lang.org/download.html</a></p>
<p>The detailed JIRA release notes can be found here:
<a href="https://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20491">https://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20491</a></p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #31</title><link>https://glaforge.dev/posts/2014/07/22/groovy-weekly-31/</link><pubDate>Tue, 22 Jul 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/22/groovy-weekly-31/</guid><description>&lt;p>The news keep flowing even in the summer!&lt;/p>
&lt;p>It’s probably not new to you, my readers, but Groovy and Grails are kick-ass technologies, as reported by the RebelLabs report.&lt;/p>
&lt;p>And did you know that Groovy is now the default scripting technology in the popular ElasticSearch? Or used by Jenkins in its Job DSL?&lt;/p>
&lt;p>We even have rumors, with the potential acquisition of Groovy-powered SmartThings IoT platform by Samsung!&lt;/p>
&lt;p>We also have more videos available of presentations from GR8Conf Europe, to prepare yourself for those who are attending GR8Conf US next week! See you there!&lt;/p></description><content:encoded>
<![CDATA[<p>The news keep flowing even in the summer!</p>
<p>It’s probably not new to you, my readers, but Groovy and Grails are kick-ass technologies, as reported by the RebelLabs report.</p>
<p>And did you know that Groovy is now the default scripting technology in the popular ElasticSearch? Or used by Jenkins in its Job DSL?</p>
<p>We even have rumors, with the potential acquisition of Groovy-powered SmartThings IoT platform by Samsung!</p>
<p>We also have more videos available of presentations from GR8Conf Europe, to prepare yourself for those who are attending GR8Conf US next week! See you there!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://github.com/groovy/groovy-eclipse/wiki/Groovy-Eclipse-2.9.0-Release-Notes">Groovy Eclipse 2.9.0</a> released</li>
<li><a href="https://spring.io/blog/2014/07/21/spring-framework-4-1-release-candidate-available">Spring Framework 4.1 RC</a> is out with Spring Web MVC support for Groovy markup templates</li>
<li>Andrés Almiray created a <a href="https://github.com/aalmiray/stats-gradle-plugin">Gradle Stats plugin</a> to gather some metrics on your project</li>
<li>Andy Wilkinson released a plugin to <a href="https://github.com/spring-gradle-plugins/dependency-management-plugin">add Maven-like dependency management to Gradle</a></li>
<li>New version of the <a href="https://twitter.com/grooscript/status/491538345265491969">GrooScript Grails 2.4 plugin</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Rebel Labs selects <a href="http://zeroturnaround.com/rebellabs/10-kick-ass-technologies-modern-developers-love/">Groovy and Gradle in their 10 kick-ass technologies</a> developers love</li>
<li><a href="https://twitter.com/robertoguerra19/status/491246418137714688">ElasticSearch&rsquo;s default scripting engine is now Groovy</a></li>
<li>Guillaume Laforge worked on making the <a href="http://beta.groovy-lang.org/">future Groovy website fully responsive</a></li>
<li>Rumor has it that Internet of Things startup <a href="http://gigaom.com/2014/07/15/if-samsung-buys-smartthings-its-a-win-for-both/">SmartThings using Groovy for orchestrating &ldquo;things&rdquo; in talks to be acquired by Samsung</a> for $200 million</li>
<li>A Gradle plugin to <a href="https://code.google.com/p/gradle-macappbundle/">bundle MacOS X applications of your Groovy / Java project</a></li>
<li><a href="https://github.com/grails/grails-core">Grails just crossed the 1000 stars on Github</a>, still time to star it!</li>
<li>Jacob Aae Mikkelsen is back with the <a href="http://grydeske.net/news/show/53">Grails Diary</a>!</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Cédric Champeau describes the <a href="http://melix.github.io/blog/2014/07/new-groovy-website.html">technical details of the new Groovy website</a></li>
<li>DrDobbs features an article by Benjamin Muschko on <a href="http://www.drdobbs.com/open-source/writing-build-scripts-with-gradle/240168648">writing build scripts with Gradle</a></li>
<li>Andrés Almiray inaugurates a series of blog posts on cool Gradle plugins, starting with the <a href="http://www.jroller.com/aalmiray/entry/gradle_glam_versions">versions Gradle plugin</a></li>
<li>André Steingreß on <a href="http://blog.andresteingress.com/2014/07/22/spock-junit-rules/">JUnit rules with Spock</a></li>
<li>The <a href="http://www.oraclejavamagazine-digital.com/javamagazine/july_august_2014/?sub_id=BUXrE7pqvqBhE&amp;folio=67#pg68">Java magazine mentions Groovy</a> in two articles, one on JavaFX showing how neat GroovyFX is, and the other on cool aspects of various alternative languages</li>
<li>Peter Ledbrook explains how to <a href="http://blog.cacoethes.co.uk/groovyandgrails/adding-spring-security-users-in-bulk-in-grails">add Spring Security users in bulk in your Grails</a> application</li>
<li>Rob Fletcher looks into <a href="http://blog.freeside.co/2014/07/21/closures-and-field-visibility/">Groovy closures and field visibility</a></li>
<li>The Gretty project can now <a href="http://akhikhl.github.io/gretty-doc/Product-generation.html">generate runnable products</a></li>
<li>Using <a href="http://www.intelligrape.com/blog/2014/07/17/using-spring-events-in-grails/">Spring events in Grails</a> by Madhave Khanna</li>
<li>Craig Atkinson blogs about <a href="http://www.objectpartners.com/2014/07/15/grails-api-functional-testing/">Grails API functional testing</a></li>
<li>Marco Pas details an issue when <a href="http://mpas.github.io/post/2014/07/upgrade-grails-2.4.2/">migrating from Grails 2.3.8 to 2.4.2</a></li>
<li>Igor Shults illustrates <a href="http://www.objectpartners.com/2014/07/09/groovys-with-and-multiple-assignment/">Groovy’s with() and multiple assignments</a></li>
<li>Ravi Kumar on the <a href="http://www.oodlestechnologies.com/blogs/Importance-of-Grails-asset-pipeline-plugin">importance of the Grails asset pipeline</a></li>
<li>Archna Dhingra writes about <a href="http://www.oodlestechnologies.com/blogs/Ways-to-access-grailsApplication-in-Grails-Project">ways to access grailsApplication in Grails</a></li>
<li><a href="http://www.oodlestechnologies.com/blogs/Create-Default-Data-and-Test-Data-the-Easy-Way-Using-Fixture-and-Build-Test-Data-plugins">Create default data and test data the easy way using fixture and build test data plugins</a> by Ankit Nigam</li>
<li>Shiv kumar explains how to <a href="http://www.oodlestechnologies.com/blogs/How-to-use-handlebars-in-grails">use handlebars in Grails</a></li>
<li>Using the <a href="http://www.oodlestechnologies.com/blogs/Drools-Planner-Example-using-Grails">Drools Planner from Grails</a> by Alok Kumar Swain</li>
</ul>
<h2 id="presentations--gr8conf-europe-2014">Presentations — GR8Conf Europe 2014</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=LZQ-1f9RGqg&amp;feature=youtu.be&amp;a">Cut your Grails application to pieces, build feature plugins</a>, by Göran Ehrsson</li>
<li><a href="https://www.youtube.com/watch?v=4LLQeMcFUpY&amp;feature=youtu.be&amp;a">Micro-service architecture with Spring Boot, Groovy</a> and friends, by Marco Vermeulen</li>
<li><a href="https://www.youtube.com/watch?v=McFoNTKD8Q0&amp;feature=youtu.be&amp;a">Functional testing your Grails apps with Geb</a>, by Collin Harrington</li>
<li><a href="https://www.youtube.com/watch?v=d_w7v-Cy8q4&amp;feature=youtu.be&amp;a">Performance tuning Grails applications</a>, by Lari Hotari, at GR8Conf Europe 2014</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau claims that <a href="https://twitter.com/cedricchampeau/status/489316376696598528">if Netflix can use Groovy, you can too</a>!</li>
<li>Kohsuke Kawaguchi invites us to play in the playground of the <a href="https://twitter.com/kohsukekawa/status/489333038573039617">Groovy-based Jenkins job DSL plugin</a></li>
<li>Cédric Champeau reminds us about the creation of a <a href="https://twitter.com/cedricchampeau/status/489310387272421376">HipChat public channel for Groovy</a> if you want to chat with Groovy users and developers</li>
<li>Marco Vermeulen realizes that <a href="https://twitter.com/marcovermeulen/status/491232279008911361">Spring Boot supports Spock for testing</a> out of the box</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Michael van Niekerk created a <a href="https://github.com/mvniekerk/GradleGroovyRobot">proof of concept running a Groovy application on iOS</a></li>
<li>The <a href="https://twitter.com/grooscript/status/489190810282643456">2048 web game developed with GrooScript</a></li>
<li>Cédric Champeau shares a snippet to <a href="https://gist.github.com/melix/628b2f04f83f7f9721b8">dump the AST transformations being used</a></li>
<li>Guillaume Laforge is contributing <a href="https://github.com/isagalaev/highlight.js/pull/512/files">Groovy syntax highlighting support to the Highlight.js</a> JavaScript syntax highlighting library</li>
<li>Danny Hyun created a <a href="https://github.com/danhyun/ratpack-pixel-service">&ldquo;pixel service&rdquo; with Ratpack</a> for user tracking</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://twitter.com/gr8confus/status/491629430700535809">JFrog is a sponsor of GR8Conf US</a> next week</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #30</title><link>https://glaforge.dev/posts/2014/07/15/groovy-weekly-30/</link><pubDate>Tue, 15 Jul 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/15/groovy-weekly-30/</guid><description>&lt;p>The &lt;a href="http://beta.groovy-lang.org/">new Groovy website&lt;/a> announced last week keeps on improving, thanks to your feedback and already many contributions through pull requests on Github!&lt;/p>
&lt;p>To wet your appetite for GR8Conf US 2014 at the end of the month, we also have more GR8Conf Europe 2014 videos published last week!&lt;/p>
&lt;h2 id="releases">Releases&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/martinlippert/status/487631673836965888">Groovy Grails ToolSuite 3.6.0 out with Groovy 2.3 and Grails 2.4&lt;/a> support on Eclipse 4.4&lt;/li>
&lt;li>&lt;a href="https://twitter.com/grooscript/status/488453854032560128">GrooScript 0.5.2&lt;/a> is out&lt;/li>
&lt;li>The &lt;a href="http://beta.groovy-lang.org/download.html">Windows installer for Groovy 2.3.4&lt;/a> is available thanks to Joachim Baumann&lt;/li>
&lt;li>Andrés Almiray released the &lt;a href="https://twitter.com/aalmiray/status/487255344234905600">Asciidoctor Gradle plugin v0.7.3&lt;/a> which is compatible with JDK 6+&lt;/li>
&lt;/ul>
&lt;h2 id="news">News&lt;/h2>
&lt;ul>
&lt;li>Kunal Dabir created an “&lt;a href="https://github.com/kdabir/awesome-groovy">awesome-groovy&lt;/a>” Github project providing a curated list of interesting Groovy-based projects&lt;/li>
&lt;/ul>
&lt;h2 id="articles">Articles&lt;/h2>
&lt;ul>
&lt;li>Benjamin Muschko explains &lt;a href="http://www.drdobbs.com/jvm/why-build-your-java-projects-with-gradle/240168608">why build your Java projects with Gradle&lt;/a> rather than Ant or Maven&lt;/li>
&lt;li>Guillaume Laforge gathered the &lt;a href="https://glaforge.dev/posts/2014/07/13/feedback-and-actions-for-the-new-groovy-website/">feedback the team received about the new Groovy website&lt;/a>&lt;/li>
&lt;li>Roberto Guerra writes about &lt;a href="http://blog.stumblingoncode.com/posts/2014-07-11-ratpack-templates.html">Ratpack&amp;rsquo;s templates&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.sven-ehrke.de/asciidocblog/switch_dependencies_binary_source.html">Use Gradle to switch a dependency from binary to source&lt;/a> mode and back, by Sven Ehrke&lt;/li>
&lt;li>Grails Goodness by MrHaki
&lt;ul>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-change-response-formats.html">change response formats in RestfulController&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-custom-controller-class.html">custom controller class with resource Annotation&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-using-converter-named.html">using converter named configurations with default renderers&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Pawel Oczadly reveals a Gradle tip: &lt;a href="http://paweloczadly.github.io/dev/2014/07/09/gradle-set-different-name-for-wrapper-and-default-build-script/">set a different name for wrapper and the default build script&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="presentations--gr8conf-europe-2014">Presentations — GR8Conf Europe 2014&lt;/h2>
&lt;ul>
&lt;li>Noam Tenne delivered a &lt;a href="https://www.youtube.com/watch?v=GfIhxi7L6R0&amp;amp;feature=youtu.be&amp;amp;a">fantastic Groovy puzzler talk&lt;/a>&lt;/li>
&lt;li>Graeme Rocher talks about &lt;a href="https://www.youtube.com/watch?v=NZH9n3s8Ai0&amp;amp;feature=youtu.be&amp;amp;a">Grails 3 and beyond&lt;/a>&lt;/li>
&lt;li>Andrés Almiray on &lt;a href="https://www.youtube.com/watch?v=hrM_AtD5eCw&amp;amp;feature=youtu.be&amp;amp;a">what&amp;rsquo;s new and coming in Griffon&lt;/a>&lt;/li>
&lt;li>Lari Hotari on &lt;a href="https://www.youtube.com/watch?v=_JVeuC8R5BM&amp;amp;feature=youtu.be&amp;amp;a">Grails 3 and Ratpack&lt;/a>&lt;/li>
&lt;li>Peter Ledbrook guides you through &lt;a href="https://www.youtube.com/watch?v=pzZARB7SxdA&amp;amp;feature=youtu.be&amp;amp;a">bootstrapping your projects with Lazybones&lt;/a>&lt;/li>
&lt;li>Jeff Brown shows you how you can do &lt;a href="https://www.youtube.com/watch?v=L-sH9Bn9y_c&amp;amp;feature=youtu.be&amp;amp;a">polyglot web development with Grails 2&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="mailing-list-posts">Mailing-list posts&lt;/h2>
&lt;ul>
&lt;li>Dylan Bijnagte announces Spock-Genesis: &lt;a href="http://groovy.329449.n5.nabble.com/ANN-spock-genesis-initial-release-0-1-0-td5720402.html">data generators for data-driven / property based testing with Spock&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="google-posts">Google+ posts&lt;/h2>
&lt;ul>
&lt;li>LinYuan Zheng launched an effort to &lt;a href="https://plus.google.com/b/113675159854671799959/106541219437002995923/posts/hcKBXa3tSkW?cfem=1">translate the Groovy documentation in Chinese&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="tweets">Tweets&lt;/h2>
&lt;ul>
&lt;li>Marco Vermeulen is looking for a &lt;a href="https://twitter.com/marcoVermeulen/status/488436820456525824">PaaS sponsor to host GVM&lt;/a>&lt;/li>
&lt;li>Dan Woods measured &lt;a href="https://twitter.com/danveloper/status/488138482280189952">Ratpack&amp;rsquo;s great stable performance at 52K requests/second&lt;/a> on a large Amazon EC2 instance&lt;/li>
&lt;li>Cédric Champeau notices that in a matter of hours, the new &lt;a href="https://twitter.com/cedricchampeau/status/486627877656752128">Groovy website project on Github received 3 pull requests&lt;/a>&lt;/li>
&lt;li>As noted by Vladimír Oraný, the &lt;a href="https://twitter.com/musketyr/status/486739565265555456">&amp;ldquo;improve the doc&amp;rdquo; button on the new Groovy web site makes it easy to contribute&lt;/a> improvements to the site&lt;/li>
&lt;li>Andrés Almiray choses &lt;a href="https://twitter.com/aalmiray/status/486951644874432512">JBake and Asciidoctor for the upcoming Griffon 2.0 website&lt;/a>&lt;/li>
&lt;li>Guillaume Laforge mentions a &lt;a href="https://twitter.com/glaforge/status/487323336876515329">dozen pull requests&lt;/a> in a day following the announcement of the beta version of the new Groovy website&lt;/li>
&lt;li>Cédric Champeau published an updated &lt;a href="https://twitter.com/CedricChampeau/status/488708268181954560">Android GR8Conf agenda application built with Groovy&lt;/a> for GR8Conf US 2014&lt;/li>
&lt;li>Cédric Champeau and Robert Fletcher &lt;a href="https://twitter.com/CedricChampeau/status/488708437493420034">contrast Groovy traits and Java 8 default methods&lt;/a>&lt;/li>
&lt;li>The &lt;a href="https://twitter.com/JennStrater/status/488511425548013568">Gr8Ladies website received a nice facelift&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/ratpackweb/status/488650602230980608">Ratpack 0.9.7 will integrate with the Gradle Shadow plugin&lt;/a> for fat-jar-ing Ratpack apps&lt;/li>
&lt;li>Eric MacAdie is using &lt;a href="http://www.macadie.net/2014/07/10/using-groovy-validator-on-immutable-objects/">Groovy Validator on immutable objects&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="code-snippets">Code snippets&lt;/h2>
&lt;ul>
&lt;li>Alexander Klein is exploring &lt;a href="https://github.com/karfunkel/grooid-playground">Groovy builders for Android&lt;/a>&lt;/li>
&lt;li>Roberto Guerra shows to &lt;a href="https://github.com/uris77/ratpack-oauth-example">authenticate with via Google OAuth for your Ratpack&lt;/a> applications&lt;/li>
&lt;/ul>
&lt;h2 id="books">Books&lt;/h2>
&lt;ul>
&lt;li>Glen Smith is proud to have the &lt;a href="https://twitter.com/glen_a_smith/status/488602309123772419">Grails in Action 2nd edition book&lt;/a> in his hands for real&lt;/li>
&lt;li>&lt;a href="https://twitter.com/manningbooks/status/487923316318277632">Grails in Action 2nd edition is now available in print&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded>
<![CDATA[<p>The <a href="http://beta.groovy-lang.org/">new Groovy website</a> announced last week keeps on improving, thanks to your feedback and already many contributions through pull requests on Github!</p>
<p>To wet your appetite for GR8Conf US 2014 at the end of the month, we also have more GR8Conf Europe 2014 videos published last week!</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="https://twitter.com/martinlippert/status/487631673836965888">Groovy Grails ToolSuite 3.6.0 out with Groovy 2.3 and Grails 2.4</a> support on Eclipse 4.4</li>
<li><a href="https://twitter.com/grooscript/status/488453854032560128">GrooScript 0.5.2</a> is out</li>
<li>The <a href="http://beta.groovy-lang.org/download.html">Windows installer for Groovy 2.3.4</a> is available thanks to Joachim Baumann</li>
<li>Andrés Almiray released the <a href="https://twitter.com/aalmiray/status/487255344234905600">Asciidoctor Gradle plugin v0.7.3</a> which is compatible with JDK 6+</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Kunal Dabir created an “<a href="https://github.com/kdabir/awesome-groovy">awesome-groovy</a>” Github project providing a curated list of interesting Groovy-based projects</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Benjamin Muschko explains <a href="http://www.drdobbs.com/jvm/why-build-your-java-projects-with-gradle/240168608">why build your Java projects with Gradle</a> rather than Ant or Maven</li>
<li>Guillaume Laforge gathered the <a href="https://glaforge.dev/posts/2014/07/13/feedback-and-actions-for-the-new-groovy-website/">feedback the team received about the new Groovy website</a></li>
<li>Roberto Guerra writes about <a href="http://blog.stumblingoncode.com/posts/2014-07-11-ratpack-templates.html">Ratpack&rsquo;s templates</a></li>
<li><a href="http://www.sven-ehrke.de/asciidocblog/switch_dependencies_binary_source.html">Use Gradle to switch a dependency from binary to source</a> mode and back, by Sven Ehrke</li>
<li>Grails Goodness by MrHaki
<ul>
<li><a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-change-response-formats.html">change response formats in RestfulController</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-custom-controller-class.html">custom controller class with resource Annotation</a></li>
<li><a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-using-converter-named.html">using converter named configurations with default renderers</a></li>
</ul>
</li>
<li>Pawel Oczadly reveals a Gradle tip: <a href="http://paweloczadly.github.io/dev/2014/07/09/gradle-set-different-name-for-wrapper-and-default-build-script/">set a different name for wrapper and the default build script</a></li>
</ul>
<h2 id="presentations--gr8conf-europe-2014">Presentations — GR8Conf Europe 2014</h2>
<ul>
<li>Noam Tenne delivered a <a href="https://www.youtube.com/watch?v=GfIhxi7L6R0&amp;feature=youtu.be&amp;a">fantastic Groovy puzzler talk</a></li>
<li>Graeme Rocher talks about <a href="https://www.youtube.com/watch?v=NZH9n3s8Ai0&amp;feature=youtu.be&amp;a">Grails 3 and beyond</a></li>
<li>Andrés Almiray on <a href="https://www.youtube.com/watch?v=hrM_AtD5eCw&amp;feature=youtu.be&amp;a">what&rsquo;s new and coming in Griffon</a></li>
<li>Lari Hotari on <a href="https://www.youtube.com/watch?v=_JVeuC8R5BM&amp;feature=youtu.be&amp;a">Grails 3 and Ratpack</a></li>
<li>Peter Ledbrook guides you through <a href="https://www.youtube.com/watch?v=pzZARB7SxdA&amp;feature=youtu.be&amp;a">bootstrapping your projects with Lazybones</a></li>
<li>Jeff Brown shows you how you can do <a href="https://www.youtube.com/watch?v=L-sH9Bn9y_c&amp;feature=youtu.be&amp;a">polyglot web development with Grails 2</a></li>
</ul>
<h2 id="mailing-list-posts">Mailing-list posts</h2>
<ul>
<li>Dylan Bijnagte announces Spock-Genesis: <a href="http://groovy.329449.n5.nabble.com/ANN-spock-genesis-initial-release-0-1-0-td5720402.html">data generators for data-driven / property based testing with Spock</a></li>
</ul>
<h2 id="google-posts">Google+ posts</h2>
<ul>
<li>LinYuan Zheng launched an effort to <a href="https://plus.google.com/b/113675159854671799959/106541219437002995923/posts/hcKBXa3tSkW?cfem=1">translate the Groovy documentation in Chinese</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Marco Vermeulen is looking for a <a href="https://twitter.com/marcoVermeulen/status/488436820456525824">PaaS sponsor to host GVM</a></li>
<li>Dan Woods measured <a href="https://twitter.com/danveloper/status/488138482280189952">Ratpack&rsquo;s great stable performance at 52K requests/second</a> on a large Amazon EC2 instance</li>
<li>Cédric Champeau notices that in a matter of hours, the new <a href="https://twitter.com/cedricchampeau/status/486627877656752128">Groovy website project on Github received 3 pull requests</a></li>
<li>As noted by Vladimír Oraný, the <a href="https://twitter.com/musketyr/status/486739565265555456">&ldquo;improve the doc&rdquo; button on the new Groovy web site makes it easy to contribute</a> improvements to the site</li>
<li>Andrés Almiray choses <a href="https://twitter.com/aalmiray/status/486951644874432512">JBake and Asciidoctor for the upcoming Griffon 2.0 website</a></li>
<li>Guillaume Laforge mentions a <a href="https://twitter.com/glaforge/status/487323336876515329">dozen pull requests</a> in a day following the announcement of the beta version of the new Groovy website</li>
<li>Cédric Champeau published an updated <a href="https://twitter.com/CedricChampeau/status/488708268181954560">Android GR8Conf agenda application built with Groovy</a> for GR8Conf US 2014</li>
<li>Cédric Champeau and Robert Fletcher <a href="https://twitter.com/CedricChampeau/status/488708437493420034">contrast Groovy traits and Java 8 default methods</a></li>
<li>The <a href="https://twitter.com/JennStrater/status/488511425548013568">Gr8Ladies website received a nice facelift</a></li>
<li><a href="https://twitter.com/ratpackweb/status/488650602230980608">Ratpack 0.9.7 will integrate with the Gradle Shadow plugin</a> for fat-jar-ing Ratpack apps</li>
<li>Eric MacAdie is using <a href="http://www.macadie.net/2014/07/10/using-groovy-validator-on-immutable-objects/">Groovy Validator on immutable objects</a></li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Alexander Klein is exploring <a href="https://github.com/karfunkel/grooid-playground">Groovy builders for Android</a></li>
<li>Roberto Guerra shows to <a href="https://github.com/uris77/ratpack-oauth-example">authenticate with via Google OAuth for your Ratpack</a> applications</li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li>Glen Smith is proud to have the <a href="https://twitter.com/glen_a_smith/status/488602309123772419">Grails in Action 2nd edition book</a> in his hands for real</li>
<li><a href="https://twitter.com/manningbooks/status/487923316318277632">Grails in Action 2nd edition is now available in print</a></li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Feedback and actions for the new Groovy website</title><link>https://glaforge.dev/posts/2014/07/13/feedback-and-actions-for-the-new-groovy-website/</link><pubDate>Sun, 13 Jul 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/13/feedback-and-actions-for-the-new-groovy-website/</guid><description>&lt;p>In this post, I&amp;rsquo;ll sum up the feedback we&amp;rsquo;ve gathered through the mailing-lists, Twitter, Google+, blog comments, Github issues, regarding the release of the beta of the &lt;a href="http://beta.groovy-lang.org/">new Groovy website&lt;/a>.&lt;/p>
&lt;p>Overall, so far, the feedback has been very positive, and people are very excited about the fresher and more modern look of the website, as well as happy to find relevant information more easily in a couple of clicks.&lt;/p>
&lt;p>But like anything, there are various aspects we can improve! Some ideas exposed below were already elements we already had on our roadmap, or on the back-burner as nice-to-have’s, but it’s worth sharing them here and comment on them.&lt;/p></description><content:encoded>
<![CDATA[<p>In this post, I&rsquo;ll sum up the feedback we&rsquo;ve gathered through the mailing-lists, Twitter, Google+, blog comments, Github issues, regarding the release of the beta of the <a href="http://beta.groovy-lang.org/">new Groovy website</a>.</p>
<p>Overall, so far, the feedback has been very positive, and people are very excited about the fresher and more modern look of the website, as well as happy to find relevant information more easily in a couple of clicks.</p>
<p>But like anything, there are various aspects we can improve! Some ideas exposed below were already elements we already had on our roadmap, or on the back-burner as nice-to-have’s, but it’s worth sharing them here and comment on them.</p>
<p>Also worth noting that in less than 12 hours after the announcement of the beta of the new website, we received several contributions on Github for fixing some typos or broken sentences, as well as some new content like a new book in the learn section.</p>
<h2 id="responsive-design">Responsive design</h2>
<p>Although some elements of the design are responsive (with CSS media queries), various aspects of the site are clearly not responsive and don’t degrade well and display nicely on mobile devices like phones and tablets.</p>
<p>For example, the menu doesn’t shrink down into a menu widget like is pretty common nowadays, and it’s probably the reason why the width of text content containers also doesn’t shrink accordingly, letting the text become very small and unreadable on smaller screens.</p>
<h2 id="concrete-feedback-mentioned">Concrete feedback mentioned:</h2>
<ul>
<li>scrolling lagging on mobile safari</li>
<li>from the US the site appeared slow (our hoster is in Germany)</li>
<li>text too small on mobile devices</li>
<li>not fully responsive</li>
</ul>
<p>Help needed from frontend-savy persons to help us:</p>
<ul>
<li>improve the responsiveness of the screen for mobile devices</li>
<li>optimize the size so it loads and renders faster</li>
</ul>
<p>Actions:</p>
<ul>
<li>we can cache the fonts coming from Google Fonts and serve them from the website</li>
<li>decrease the size of pictures</li>
<li>use sprites for the logos (“they use Groovy”)</li>
</ul>
<h2 id="content-improvements">Content improvements</h2>
<p>Here are some suggestions for content that users would like to see.</p>
<h3 id="getting-started-guide">Getting started guide</h3>
<p>We’re missing a “getting started guide” that would take users through the steps to download and install Groovy, and then showing them the basics of the language.</p>
<p>We could perhaps integrate a guide like the <a href="http://gr8labs.org/getting-groovy/">getting started guide</a> from MrHaki.</p>
<p>Additionally, it would be nice if the samples in such a guide could be edited live, or played with by the readers. See next section on this topic.</p>
<p>Actions:</p>
<ul>
<li>write a getting started guide distinct from the reference documentation for a fast-track learning approach</li>
</ul>
<p>Possible improvements:</p>
<ul>
<li>The website could also provide some introductions to the 6 key areas listed, that we could link directly from the front page:
<ul>
<li>“flat learning curve” — mention how it’s familiar to Groovy developers</li>
<li>“smooth Java integration” — mention how you can mix Groovy and Java together</li>
<li>“vibrant and rich ecosystem” — perhaps just link to the “ecosystem” section</li>
<li>“powerful feature” — perhaps lead to the “getting started guide” that would give a quick intro to those powerful features</li>
<li>“domain-specific languages” — perhaps a link to the DSL user guide from the reference documentation or a simpler getting started page on the topic</li>
<li>“scripting &amp; test glue” — show some samples of how to use Groovy for command-line scripting / automation, and samples of readable tests (likely Spock)</li>
</ul>
</li>
</ul>
<h3 id="interactive-code-samples">Interactive code samples</h3>
<p>We received several related comments regarding the fact we’re missing code samples (apart from inside the documentation of course):</p>
<ul>
<li>show code samples from the front page to show readers what Groovy looks like</li>
<li>some kind of built-in terminal or shell where we could play with Groovy code in a sandbox in the browser</li>
<li>being able to run the code samples we display</li>
</ul>
<p>Actions:</p>
<ul>
<li>add code sample on the front page</li>
<li>add a built-in shell</li>
</ul>
<h2 id="missing-blog">Missing blog</h2>
<p>Currently, we don’t yet have a news or blog section in the website, as we haven’t yet decided how we would integrate that in the website. It’s important to be able to announce new releases, interesting articles, etc, to our community, beyond the mailing-lists or the social networks through which we usually communicate.</p>
<p>Actions:</p>
<ul>
<li>decide what technical approach to take to offer news and posts in the website</li>
<li>implement / integrate the desired blogging approach</li>
</ul>
<p>Possible future improvements:</p>
<ul>
<li>also integrate the Groovy Weekly news as blog posts</li>
</ul>
<h2 id="user-groups">User groups</h2>
<p>The Groovy user groups are clearly an important part of the success of Groovy and its community. The website currently doesn’t have a user group section as initially planned.</p>
<p>Actions:</p>
<ul>
<li>add a new section in the community about user groups (2014-07-10)</li>
</ul>
<p>Possible future improvements:</p>
<ul>
<li>show an interactive map of the location of the various groups</li>
<li>let user groups contribute calendar events that could be displayed in the events page</li>
</ul>
<h2 id="sponsors-not-listed">Sponsors not listed</h2>
<p>In the old website, we had some places where we could mention our sponsors, but the new website currently doesn’t mention them.</p>
<p>Existing sponsors of the project:</p>
<ul>
<li>Pivotal — hire several Groovy core team members<br />
The Pivotal logo is already present in the design, in the footer of the page.</li>
<li>JetBrains — provide us with free TeamCity and IntelliJ IDEA Ultimate licenses, and cover the cost of our server (hosting the CI server, the website, and the documentation)</li>
<li>JFrog — provide the Bintray and Artifactory OSS infrastructure for deploying our releases and snapshots</li>
</ul>
<p>Actions:</p>
<ul>
<li>see how we can integrate our sponsors, in the footer or in a dedicated section (2014-07-13)</li>
</ul>
<h2 id="videos">Videos</h2>
<p>Interesting videos related to the Groovy project have been recorded at conferences like GR8Conf, Greach, SpringOne2GX and other Java-related conferences. Such videos could also be a key asset in the learning experience of Groovy users.</p>
<p>Actions:</p>
<ul>
<li>add a video section in the learning area of the website</li>
</ul>
<h2 id="other-ecosystem-projects">Other ecosystem projects</h2>
<p>The “ecosystem” page currently lists just a handful of well-known Groovy ecosystem projects. Other module / projects authors would be interested in appearing in that section as well. But the problem (in the old website for instance) is that often several small projects are not really alive anymore, and it gives a bad feeling about the state of the community overall.</p>
<p>Actions:</p>
<ul>
<li>see how we can get additional / lesser-known projects up in the community section, perhaps with another page for contributed projects</li>
</ul>
<p>Possible improvements:</p>
<ul>
<li>see if we can integrate with an API like Ohloh’s which provide an indicator of project “health”, which could allow us to automatically list / remove projects which are not deemed healthy or need some more love</li>
</ul>
<p>There are various indicators that we can take into account for evaluating the liveliness of a project:</p>
<ul>
<li>its Ohloh health status mentioned above</li>
<li>the date of the latest commits</li>
<li>the date of the latest release</li>
<li>recent successful Travis / Jenkins / other CI build status</li>
</ul>
<h2 id="translations">Translations</h2>
<p>Several persons mentioned their desire to have the documentation translated in other languages, and some even proposed their help.</p>
<p>On the technical side, we haven’t put anything in place neither for the website nor for the documentation to be able to provide translated website and documentation.</p>
<p>On the human side, I know it’s already difficult to even just finish the documentation, that I’m a bit doubtful that we will ever get to a point where all the documentation is also translated, and in “sync” with the English base documentation. Some efforts have been made historically on the Groovy website or Grails documentation, but none ever got to completion.</p>
<p>Possible future actions:</p>
<ul>
<li>investigate how we can provide the technical infrastructure for authoring translations and publish them</li>
</ul>
<h2 id="usability">Usability</h2>
<p>Apart from the important responsiveness aspect we’ve outlined already, there are other areas where we can improve the usability of the website.</p>
<h2 id="subdomain-usage">Subdomain usage</h2>
<p>Currently, the website is in beta mode under the “beta” subdomain, and the documentation is also available through that subdomain as well as under “docs”.</p>
<p>Actions:</p>
<ul>
<li>once the website exits the beta status, we’ll be able to have dedicated “www” and “docs” subdomains to clearly demarcate website and documentation (which is important for search, see below)</li>
</ul>
<h2 id="search">Search</h2>
<p>The search is powered by Google Custom Search and is even faceted so that users can search only mailing-list posts, JIRA issues, documentation, etc.</p>
<p>The faceted aspect can be improved a bit when we properly separate and finalize the subdomains, with “www” for the website and “docs” for the reference documentation.</p>
<p>Beside that, the main gripe is the fact that when a user clicks on a search result, the browser moves to that result page, but when the user tracks back to the previous page, the results are gone, showing blank search field and results — ie. the search results are lost.</p>
<p>Actions:</p>
<ul>
<li>use target _blank to open a new tab in the browser for the search results (2014-07-13)</li>
</ul>
<h2 id="ui-inconsistencies">UI inconsistencies</h2>
<p>The website and the reference documentation share a similar look’n feel, with the same fonts and colors, but there are still differences like the color of the links, the font size, the spacing between elements, etc.</p>
<p>Action:</p>
<ul>
<li>futher work to homogenized both the website and the reference documentation</li>
</ul>
<h2 id="miscellaneous">Miscellaneous</h2>
<p>Other feedback we’ve received and other ideas but not listed in the previous sections:</p>
<ul>
<li>The “definition” of what Groovy is might perhaps be a bit too long or complicated, as Groovy is by nature a “multi-faceted” language with different flavors (static / dynamic / optional typing, and OO / imperative / functional language)</li>
<li>In the “they use Groovy” section</li>
<li>EADS is now actually the Airbus Group, so the logo could be changed (2014-07-10)</li>
<li>Best Buy are heavy users of Groovy and their logo could be added (2014-07-10)</li>
<li>Suggestion to use a different domain name, with .io, without dashes, etc.</li>
<li>Add a sitemap.xml file to help with search engine indexing</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #29</title><link>https://glaforge.dev/posts/2014/07/08/groovy-weekly-29/</link><pubDate>Tue, 08 Jul 2014 03:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/08/groovy-weekly-29/</guid><description>&lt;p>Keywords for today: beta Groovy website, Gradle roadmap, GR8Conf presentations!&lt;/p>
&lt;p>It’s a launch day! The launch of the beta of the Groovy website, mentioned in the news section (and also a bug fix release with Groovy 2.3.4).&lt;/p>
&lt;p>In the article section, you’ll find the link to Hans Dockter’s latest post on the Gradle forums which details what you can expect from future Gradle versions, and it’s very promising: think performance, parallelization, caching, sharing and tooling!&lt;/p></description><content:encoded>
<![CDATA[<p>Keywords for today: beta Groovy website, Gradle roadmap, GR8Conf presentations!</p>
<p>It’s a launch day! The launch of the beta of the Groovy website, mentioned in the news section (and also a bug fix release with Groovy 2.3.4).</p>
<p>In the article section, you’ll find the link to Hans Dockter’s latest post on the Gradle forums which details what you can expect from future Gradle versions, and it’s very promising: think performance, parallelization, caching, sharing and tooling!</p>
<p>In the presentation section, the GR8Conf Europe crew has been editing and publishing the videos of presentations given at last month conference in Denmark. So you have a few hours worth of Groovy content to watch! Also for those in the US, don’t forget that GR8Conf US is fast approaching, at the end of the month!</p>
<h2 id="releases">Releases</h2>
<ul>
<li>Guillaume Laforge announces the <a href="https://glaforge.dev/posts/2014/07/08/groovy-2-3-4-is-out/">release of Groovy 2.3.4</a>, with further bug fixes in the anonymous inner class support and also compatibility between AST transformations and static compilation</li>
<li>The <a href="http://docs.codehaus.org/display/GRIFFON/2014/07/03/Griffon+2.0.0.BETA3+Released">third beta of Griffon 2.0</a> is out</li>
<li><a href="https://spring.io/blog/2014/07/08/spring-boot-1-1-4-released">Spring Boot 1.1.4</a> released</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>Guillaume Laforge announces the <a href="https://glaforge.dev/posts/2014/07/08/a-new-groovy-website-in-beta/">beta of the new Groovy website</a>, a project hosted on Github people can contribute to, built with Groovy and Gradle</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Hans Dockter reveals the upcoming plans for the <a href="http://forums.gradle.org/gradle/topics/revolutionary_new_gradle_features_on_the_2014_roadmap">revolutionary features on the Gradle roadmap</a></li>
<li>Robert Fletcher explains one of the <a href="http://blog.freeside.co/post/91127019156/compilestatic-and-polymorphic-method-dispatch">subtle differences in behavior between &ldquo;dynamic Groovy&rdquo; and statically compiled Groovy</a> code in the case of multi-methods</li>
<li>Mark Perry&rsquo;s ongoing functional explorations with <a href="http://mperry.github.io/2014/07/02/groovy-applicatives.html">Groovy applicatives</a></li>
<li>Andrés Almiray explains how to <a href="http://www.jroller.com/aalmiray/entry/getting_started_with_griffon_and">get started with Griffon and JavaFX</a></li>
<li>Andrés Almiray delivers the second part of <a href="http://www.jroller.com/aalmiray/entry/getting_started_with_griffon_and1">Griffon and JavaFX</a> article</li>
<li>Pawel Oczadly details how to <a href="http://paweloczadly.github.io/dev/2014/07/03/gradle-how-to-use-variables-and-methods-from-another-gradle-file/">use variables and methods from another Gradle build</a> file</li>
<li>MrHaki&rsquo;s Grails Goodness: <a href="http://mrhaki.blogspot.fr/2014/07/grails-goodness-enable-accept-header.html">enable accept header for user agent requests</a></li>
</ul>
<h2 id="presentations--gr8conf-europe-2014-videos">Presentations — GR8Conf Europe 2014 videos</h2>
<ul>
<li>Alexander Klein on <a href="https://twitter.com/gr8conf/status/486086506608459780">Vert.x using Groovy</a></li>
<li>Andrés Almiray gives an <a href="https://twitter.com/gr8conf/status/485050696236929024">overview of the Groovy Ecosystem</a></li>
<li>Peter Ledbrook talking about <a href="https://twitter.com/gr8conf/status/485044283917885440">application architectures with Grails</a></li>
<li>Jeff Brown talks about <a href="https://twitter.com/gr8conf/status/486085532137426944">metaprogramming with the Groovy runtime</a> (part 1 of 2)</li>
<li>Jeff Brown&rsquo;s second part on <a href="https://twitter.com/gr8conf/status/486085531193708544">metaprogramming with the Groovy runtime</a> (part 2 of 2)</li>
<li>Marco Vermeulen on <a href="https://twitter.com/gr8conf/status/486425245717233664">BDD using Groovy and Cucumber</a></li>
<li>Grails and Devops, <a href="https://twitter.com/gr8conf/status/486424949058322432">continuous integration and delivery in the cloud</a> by Benoît Hédiard</li>
<li>Guillaume Laforge on <a href="https://twitter.com/gr8conf/status/485475403864104960">Groovy in 2014 and beyond</a></li>
<li>Robert Fletcher demonstrates <a href="https://twitter.com/gr8conf/status/485335393315717120">hybrid client/server view rendering with Grails</a></li>
<li>Claus Ibsen on <a href="https://twitter.com/gr8conf/status/485166038510620672">integration with Apache Camel and Groovy</a></li>
<li>Jorge Franco Leza delves into <a href="https://twitter.com/gr8conf/status/485160698205327360">GrooScript, the Groovy to JavaScript transpiler</a></li>
</ul>
<h2 id="google-post">Google+ post</h2>
<ul>
<li>Guillaume Barthe announces a <a href="https://plus.google.com/u/0/b/100737441520485064384/100737441520485064384/posts">Google+ page dedicated to French-speaking Groovy and Grails users</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau reveals that the <a href="https://twitter.com/cedricchampeau/status/486583986979090432">new Groovy website is making use of Groovy&rsquo;s new markup template engine</a></li>
<li><a href="https://twitter.com/ratpackweb/status/484289839500513280">Ratpack 0.9.7 will support Groovy 2.3&rsquo;s markup template engine</a></li>
<li>In its latest update, Apple updated the range operator of the <a href="https://twitter.com/practicalswift/status/486479638760140800">Swift language to borrow again from Groovy with the ..&lt; notation</a> for ranges with excluded upper bound</li>
<li>Eric MacAdie works on a <a href="http://www.macadie.net/2014/07/02/more-on-groovy-validation/">Groovy validator project</a></li>
<li>Cédric Champeau <a href="https://twitter.com/cedricchampeau/status/485360654266945536">updated the GR8Conf Agenda Groovy / Android application</a> to work with GR8Conf US 2014</li>
<li>The new Groovy documentation is built with Asciidoctor, and Dan Allen shows how this <a href="https://twitter.com/mojavelinux/status/485197497594171392">Groovy documentation can be rendered in ePub format</a></li>
<li>Yoav Landman mentions <a href="https://twitter.com/_yoav_/status/485185270283657216">enhancements in the Gradle Bintray plugin</a> that will help auto-inclusion of Gradle plugins in the Gradle plugin portal</li>
<li><a href="https://twitter.com/gvmtool/status/486585262580195328">Spring Boot 1.1.4</a> available in GVM</li>
<li><a href="https://twitter.com/gvmtool/status/486584287689719810">Groovy 2.3.4</a> is available through GVM</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Paul Osborne shows a nice usage of <a href="https://twitter.com/posbornet/status/484294950772023296">multiple assignment to split a date string</a> and fetch its mont and year elegantly</li>
<li>Andrés Almiray <a href="https://twitter.com/aalmiray/status/484778706339381248">contrasts a Griffon / JavaFX application written in Groovy and in Java</a></li>
<li>A new example <a href="https://github.com/dbirtwell/trackmystuff_ratpack_with_db">Ratpack application using GStorm for persistence</a> by David Birtwell</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>More teasers for the <a href="https://storify.com/glaforge/more-gr8conf-us-teasers">upcoming presentations at GR8Conf US 2014</a>, with new talks announced</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy 2.3.4 is out</title><link>https://glaforge.dev/posts/2014/07/08/groovy-2-3-4-is-out/</link><pubDate>Tue, 08 Jul 2014 02:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/08/groovy-2-3-4-is-out/</guid><description>&lt;p>We&amp;rsquo;re happy to announce the bug-fix release of Groovy 2.3.4.&lt;/p>
&lt;p>In store, we continued on our hunt of bugs related to anonymous inner classes, and we have a big work on the compatibility of our AST transformations with static compilation.&lt;br />
&lt;a href="http://beta.groovy-lang.org/download.html">Download Groovy 2.3.4&lt;/a> from our &lt;a href="http://beta.groovy-lang.org/">new website&lt;/a>:&lt;/p>
&lt;p>You can read the &lt;a href="https://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;amp;version=20432">JIRA release notes&lt;/a>.&lt;/p>
&lt;p>Thanks to all those who contributed to this release!&lt;/p>
&lt;p>Keep on Groovy-ing!&lt;/p></description><content:encoded>
<![CDATA[<p>We&rsquo;re happy to announce the bug-fix release of Groovy 2.3.4.</p>
<p>In store, we continued on our hunt of bugs related to anonymous inner classes, and we have a big work on the compatibility of our AST transformations with static compilation.<br />
<a href="http://beta.groovy-lang.org/download.html">Download Groovy 2.3.4</a> from our <a href="http://beta.groovy-lang.org/">new website</a>:</p>
<p>You can read the <a href="https://jira.codehaus.org/secure/ReleaseNote.jspa?projectId=10242&amp;version=20432">JIRA release notes</a>.</p>
<p>Thanks to all those who contributed to this release!</p>
<p>Keep on Groovy-ing!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>A new Groovy website in beta</title><link>https://glaforge.dev/posts/2014/07/08/a-new-groovy-website-in-beta/</link><pubDate>Tue, 08 Jul 2014 01:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/08/a-new-groovy-website-in-beta/</guid><description>&lt;p>The past few weeks, the Groovy team has been working on a new website for the project.&lt;/p>
&lt;p>Without further ado, let me introduce you to its beta: &lt;a href="http://beta.groovy-lang.org">http://beta.groovy-lang.org&lt;/a>.&lt;/p>
&lt;p>The website is actually a &lt;a href="https://github.com/groovy/groovy-website">Groovy and Gradle application that you can fork&lt;/a> and help us improve! So don&amp;rsquo;t hesitate to contribute fixes for things like typos or broken English, or suggest new relevant sections, etc. Your help will be welcome. Notice the &amp;ldquo;improve this doc&amp;rdquo; buttons on all pages which lead you to the relevant Github page that you can edit live, inline, on Github (if you&amp;rsquo;ve got an account already).&lt;/p></description><content:encoded>
<![CDATA[<p>The past few weeks, the Groovy team has been working on a new website for the project.</p>
<p>Without further ado, let me introduce you to its beta: <a href="http://beta.groovy-lang.org">http://beta.groovy-lang.org</a>.</p>
<p>The website is actually a <a href="https://github.com/groovy/groovy-website">Groovy and Gradle application that you can fork</a> and help us improve! So don&rsquo;t hesitate to contribute fixes for things like typos or broken English, or suggest new relevant sections, etc. Your help will be welcome. Notice the &ldquo;improve this doc&rdquo; buttons on all pages which lead you to the relevant Github page that you can edit live, inline, on Github (if you&rsquo;ve got an account already).</p>
<p>This initial design prototype was created by <a href="https://twitter.com/oodamien">Damien Vitrac</a>, who also designed the <a href="https://grails.org/">Grails</a> website. Big thanks to Damien for this great work!</p>
<p>The site is currently in beta, awaiting your feedback, and some further minor improvements. You will notice also some links in the documentation leading to 404s as we still have Asciidoctor documentation to write. This is also an area where authors are welcome to give a hand; if there are particular topics you&rsquo;d like to work on, please raise your hand.</p>
<p>Once we&rsquo;re happy with the state of this website, and also with the related documentation that still needs to be written and linked to, we&rsquo;ll be switching from beta to official mode, and <a href="https://www.groovy-lang.org">www.groovy-lang.org</a> will become the new home for the Groovy project. The old wiki documentation from Confluence will be frozen and available in PDF form should you need it.</p>
<p>We&rsquo;re looking forward to your feedback, your suggestions, and more!</p>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #28</title><link>https://glaforge.dev/posts/2014/07/01/groovy-weekly-28/</link><pubDate>Tue, 01 Jul 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/07/01/groovy-weekly-28/</guid><description>&lt;p>A big week for the Gradle team as they’ve just announced the final release of Gradle 2.0! It’s of course a very important milestone for the project, and it’s nice to see that the migration to Groovy 2.3 as a baseline.&lt;/p>
&lt;p>There are also some updates to Grails, Ratpack, and also the launch of the Spring IO platform that you might be interested in.&lt;/p>
&lt;p>You’ll see also a list of job postings for Netflix, where Groovy is deployed at a very large scale and who are always seeking Groovy talents.&lt;/p></description><content:encoded>
<![CDATA[<p>A big week for the Gradle team as they’ve just announced the final release of Gradle 2.0! It’s of course a very important milestone for the project, and it’s nice to see that the migration to Groovy 2.3 as a baseline.</p>
<p>There are also some updates to Grails, Ratpack, and also the launch of the Spring IO platform that you might be interested in.</p>
<p>You’ll see also a list of job postings for Netflix, where Groovy is deployed at a very large scale and who are always seeking Groovy talents.</p>
<h2 id="releases">Releases</h2>
<ul>
<li><a href="http://forums.gradle.org/gradle/topics/gradle_2_0_released">Gradle 2.0</a> released (with Groovy 2.3 included!)</li>
<li><a href="http://www.ratpack.io/versions/0.9.6">Ratpack 0.9.6</a> released</li>
<li><a href="https://grails.org/news/1290721">Grails 2.4.2 and 2.3.11</a> are released</li>
<li>The GrooScript Gradle pluging v0.4 is out, with Gradle 2.0 and Groovy 2.3.3</li>
<li>The <a href="https://spring.io/blog/2014/06/26/spring-io-platform-1-0-0-released">Spring IO platform 1.0</a> has just been released, aligned with the recent Groovy &amp; Grails releases</li>
<li><a href="https://twitter.com/pledbrook/status/481773978206601217">Lazybones 0.7</a> is released</li>
<li><a href="https://twitter.com/springboot/status/481569291754434560">Spring Boot 1.1.2</a> is out</li>
<li><a href="https://spring.io/blog/2014/06/27/spring-boot-1-1-3-available-now">Spring Boot 1.1.3</a> out as well!</li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>June <a href="http://www.gradleware.com/newsletter/gradleware-newsletter-june-2014/">newsletter from GradleWare</a> about all things Gradle</li>
<li><a href="http://mperry.github.io/2014/07/01/groovy-functors.html">Functors in Groovy</a> by Mark Perry</li>
<li>MrHaki&rsquo;s Spocklight: <a href="http://mrhaki.blogspot.fr/2014/06/spocklight-assign-multiple-data.html">assign multiple data variables from provider</a></li>
<li>MrHaki shows how to <a href="http://mrhaki.blogspot.fr/2014/06/spocklight-write-our-own-data-provider.html">write your own data providers for Spock</a></li>
<li>Infographic article from Cygnet Infotech on <a href="http://www.cygnet-infotech.com/infographics/legacy-system-to-grails">moving from legacy technologies towards Grails</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>Dan Woods presented <a href="https://github.com/danveloper/uberconf2014-ratpack">Ratpack</a> at UberConf 2014, with slides and demos</li>
<li>Slides and demos from Dan Woods on <a href="https://github.com/danveloper/uberconf2014-from-groovy-to-java8">introducing Groovy developers to the Java 8</a> new features, presented at UberConf 2014</li>
<li><a href="https://speakerdeck.com/jlstrater/groovy-as-a-scripting-language">Groovy as a scripting language</a>, by Jen Strater, at the GR8Ladies GR8Workshop</li>
</ul>
<h2 id="news">News</h2>
<ul>
<li>The <a href="http://groovyfx.org/docs/index.html">GroovyFX project migrated its documentation to Asciidoctor</a></li>
<li><a href="https://twitter.com/rfletcherew/status/482479225253810176">Intellij IDEA&rsquo;s new Early Access Program</a> has started, for version 14, which includes the Groovy traits support, as well as better interactions between Grails and Gradle</li>
</ul>
<h2 id="mailing-lists">Mailing-lists</h2>
<ul>
<li>Cédric Champeau announced that <a href="http://groovy.329449.n5.nabble.com/Docs-available-for-older-versions-of-Groovy-td5720254.html">older versions of the Groovy documentation</a> were now also available online</li>
</ul>
<h2 id="jobs">Jobs</h2>
<ul>
<li><a href="https://twitter.com/tmbradley73/status/481824337343684608">Netflix is still looking for Groovy talents</a></li>
<li>Daniel Jacobson from Netflix lists some of the <a href="https://gist.github.com/glaforge/e1d88601c053611678ae">job postings from Netflix where Groovy skills are needed</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Peter Ledbrook recoreded <a href="http://groovypodcast.podbean.com/e/groovy-podcast-ep-2-1403795851/">Episode 4 of the Groovy podcast</a>, including an interview with Baruch Sadogursky of JFrog, to discuss Groovy, Gradle, Bintray and more</li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Cédric Champeau points at the <a href="https://twitter.com/cedricchampeau/status/482045140547428352">Groovy support for Android</a>, for those who are disappointed that Google didn&rsquo;t announce a new language for Android</li>
<li><a href="https://twitter.com/gvmtool/status/482131395419308032">Grails 2.4.2 and 2.3.11</a> are available on GVM</li>
<li>Rob Fletcher shares a screenshot of the nice <a href="https://twitter.com/rfletcherew/status/482498222074912769">T icon for traits in IntelliJ IDEA 14</a> EAP</li>
<li><a href="https://twitter.com/gvmtool/status/481767669361045504">Lazybones 0.7</a> is available on GVM</li>
<li><a href="https://twitter.com/gvmtool/status/481767515564281856">Spring Boot 1.1.2</a> available through GVM</li>
<li>Alexander Klein explains the <a href="https://storify.com/glaforge/alexander-klein-explains-the-inception-of-the-gvm">inception of the GVM logo</a></li>
<li>A <a href="https://twitter.com/ratpackweb/status/482242234029318145">Ratpack tip</a> to enable static compilation of Ratpack scripts</li>
<li>More <a href="https://twitter.com/joanzap/status/483635064224624641">happy users of Groovy on Android</a>!</li>
<li><a href="https://twitter.com/gvmtool/status/483941783811129344">Gradle 2.0</a> is available through GVM</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Jon Brisbin is brainstorming some a <a href="https://twitter.com/j_brisbin/status/482942386843041793">Basho WebMachine Groovy</a> approach</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li><a href="https://storify.com/glaforge/gr8conf-us-programme-and-last-day-for-the-early-bi">GR8Conf US programme, sponsors, and teasing the great talks</a> you&rsquo;ll be able to attend</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy Weekly #27</title><link>https://glaforge.dev/posts/2014/06/24/groovy-weekly-27/</link><pubDate>Tue, 24 Jun 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/06/24/groovy-weekly-27/</guid><description>&lt;p>This week, I’d like to highlight how you can contribute to the Groovy project!&lt;/p>
&lt;p>The Groovy core team is a very small team, compared to the huge team a company like Oracle puts behind Java and the JVM, or Microsoft behind its languages and the .Net platform. So all contributions, in any form, count, and are important to the success and evolution of the Groovy language.&lt;/p>
&lt;p>If you want to contribute to the code of Groovy, Cédric Champeau recorded a &lt;a href="http://melix.github.io/blog/2014/06/contribute-groovy-ide.html">screencast showing how you can set up IntelliJ IDEA to be able to work on the Groovy codebase&lt;/a>.&lt;/p></description><content:encoded>
<![CDATA[<p>This week, I’d like to highlight how you can contribute to the Groovy project!</p>
<p>The Groovy core team is a very small team, compared to the huge team a company like Oracle puts behind Java and the JVM, or Microsoft behind its languages and the .Net platform. So all contributions, in any form, count, and are important to the success and evolution of the Groovy language.</p>
<p>If you want to contribute to the code of Groovy, Cédric Champeau recorded a <a href="http://melix.github.io/blog/2014/06/contribute-groovy-ide.html">screencast showing how you can set up IntelliJ IDEA to be able to work on the Groovy codebase</a>.</p>
<p>But another key area we’ve been working on for a while is the new Groovy documentation. Peter Ledbrook <a href="http://www.cacoethes.co.uk/blog/groovyandgrails/contributing-to-the-groovy-documentation">detailed how you can help us author some sections of the documentation</a>. Like Peter, you can dedicate a bit of your time on a topic of your choice, to further deepen your understanding of a particular area of Groovy and share your knowledge with your peers.</p>
<p>We’re really looking forward for your contributions!</p>
<h2 id="releases">Releases</h2>
<ul>
<li>Szczepan Faber announces <a href="http://forums.gradle.org/gradle/topics/we_need_your_help_trying_out_gradle_2_0_rc_2?rfm=1">Gradle 2.0 rc-2</a></li>
<li><a href="http://docs.codehaus.org/display/GRIFFON/2014/06/20/Griffon+2.0.0.BETA2+Released">Griffon 2.0.0 beta-2</a> released</li>
<li>An update release of all the <a href="https://twitter.com/theaviary/status/480697869767569408">Griffon 2.0.0 beta-3 lazybones templates</a></li>
<li><a href="https://groups.google.com/forum/?fromgroups#!topic/vertx/BFOjtqKeIjI">Vert.x 2.1.1</a> released</li>
<li><a href="https://twitter.com/mrundberget/status/479693228015644672">Bug fix release of the Light Table Groovy</a> client with v0.0.7</li>
<li>Peter Ledbrook released <a href="https://twitter.com/pledbrook/status/479886014085734400">v1.1 of the Lazybones Gradle plugin</a></li>
<li><a href="https://twitter.com/AndreyHihlovski/status/481088938124509184">Gretty 1.0</a> is out, with Tomcat 7 &amp; 8 support</li>
<li>Yoav Landman announces <a href="https://twitter.com/_yoav_/status/481202023258288130">version 0.4 of the Gradle Bintray plugin</a></li>
</ul>
<h2 id="articles">Articles</h2>
<ul>
<li>Peter Ledbrook explains how to <a href="http://www.cacoethes.co.uk/blog/groovyandgrails/contributing-to-the-groovy-documentation">contribute to the Groovy documentation</a></li>
<li>A <a href="http://thejavatar.com/testing-with-spock/">detailed introduction to the Spock</a> testing framework by Lukasz Janicki</li>
<li>Dan Woods explores <a href="http://www.infoq.com/articles/microframeworks1-spring-boot">micro-services with Spring Boot and Groovy</a></li>
<li>Alex Staveley writes about <a href="http://dublintech.blogspot.ie/2014/06/mongodb-and-grails.html">MongoDB and Grails</a></li>
<li>Further <a href="http://codewader.blogspot.no/2014/06/a-groovy-light-table-client-step-5.html">Gradle dependecy visualization in Light Table</a>&rsquo;s Groovy client with Dagre-D3 by Magnus Rundberget</li>
<li>MrHaki&rsquo;s Spocklight: <a href="http://mrhaki.blogspot.fr/2014/06/spocklight-ignore-specifications-based.html">ignore specifications based on conditions</a></li>
<li>Java build tools: <a href="http://www.javacodegeeks.com/2014/06/java-build-tools-ant-vs-maven-vs-gradle.html">Ant vs Maven vs Gradle</a>, by Viktor Farcic</li>
<li><a href="http://lhuet.github.io/blog/2014/06/gvm-on-beaglebone.html">Groovy, Gradle and Vert.x on Beaglebone with GVM</a> by Laurent Huet</li>
<li>Part 2 of <a href="http://www.intelligrape.com/blog/2014/06/22/oauth-2-0-using-grails-part-2/">OAuth 2 using Grails</a></li>
<li>A <a href="http://www.objectpartners.com/2014/06/18/gradle-summit-2014-recap/">recap of the Gradle Summit 2014</a> by John Engelman</li>
<li>Robert Fletcher writes about how to use <a href="http://blog.freeside.co/post/89759686171/gradle-and-groovys-invoke-dynamic-support">Groovy&rsquo;s invoke dynamic support in your Gradle projects</a></li>
<li>Robert Fletcher explicits how to <a href="http://blog.freeside.co/post/89760608881/using-groovys-invokedynamic-support-in-gradle">trigger the usage Groovy&rsquo;s invoke dynamic support in Gradle</a></li>
</ul>
<h2 id="presentations">Presentations</h2>
<ul>
<li>The <a href="http://www.cloudbees.com/sites/default/files/juc/juc2014/boston/2014-0618-Boston-Jesse_Glick-Workflow.pdf">Jenkins Workflow projects is built on Groovy</a>, for defining continous delivery pipelines and more, presentation given by Jesse Glick at the Jenkins User Conference Boston</li>
<li>Andrés Almiray <a href="http://cds.cern.ch/record/1709713">presents the Griffon desktop framework at the CERN</a></li>
</ul>
<h2 id="news">News</h2>
<ul>
<li><a href="http://grydeske.net/news/show/51">Grails Diary week 25</a> by Jacob Aae Mikkelsen</li>
<li>A <a href="http://plugins.jetbrains.com/plugin/7442?pr=idea">GMavenPlus plugin for IntelliJ IDEA</a></li>
<li>Magnus Rundberget points us at the <a href="https://github.com/rundis/LightTable-Groovy/wiki/Contribute">contribution page</a>, if you want to help improve the LightTable Groovy client</li>
<li>The new <a href="http://griffon.github.io/griffon/guide/">Griffon user guide is adopting a similar look&rsquo;n feel and style as the new Groovy documentation</a>, with Asciidoctor</li>
</ul>
<h2 id="screencasts">Screencasts</h2>
<ul>
<li>Cédric Champeau recorded a <a href="http://melix.github.io/blog/2014/06/contribute-groovy-ide.html">screencast showing how to setup IntelliJ IDEA to contribute to the Groovy project</a></li>
</ul>
<h2 id="podcasts">Podcasts</h2>
<ul>
<li>Peter Ledbrook recorded the <a href="https://twitter.com/pledbrook/status/479702066689224704">Groovy podcast episode 3</a></li>
</ul>
<h2 id="tweets">Tweets</h2>
<ul>
<li>Already <a href="https://twitter.com/cedricchampeau/status/479160332049727488">100 stars on Github for the Groovy Android plugin for Gradle</a>. You can still add your own star!</li>
<li>Robert Fletcher shares a Gradle tip on how to <a href="https://twitter.com/rfletcherew/status/479909583595790336">enable the invoke dynamic support in your Gradle build</a> when using the Groovy &ldquo;indy&rdquo; JAR</li>
<li><a href="https://twitter.com/gvmtool/status/479525410561335297">Grails 2.4.1 and 2.3.10</a> available in GVM</li>
<li>Andrés Almiray points out that <a href="https://twitter.com/aalmiray/status/479343529500872704">Griffon 2.0.0 application can be built with Maven</a></li>
<li><a href="https://twitter.com/gvmtool/status/481140386963132416">Gradle 2.0 rc-2</a> is available through GVM</li>
<li><a href="https://twitter.com/gvmtool/status/481161262626504705">GVM has a logo</a>!</li>
</ul>
<h2 id="code-snippets">Code snippets</h2>
<ul>
<li>Guillaume Laforge shows a <a href="http://groovyconsole.appspot.com/script/5643440998055936">code visitor that transforms BigDecimal literals into doubles</a></li>
<li>A tiny <a href="http://groovyconsole.appspot.com/script/5653164804014080/">wiki text converter using Groovy traits</a></li>
</ul>
<h2 id="books">Books</h2>
<ul>
<li><a href="https://twitter.com/kenkousen/status/481227428124057601">44% off on Making Java Groovy</a> by Ken Kousen</li>
</ul>
<h2 id="events">Events</h2>
<ul>
<li>Guillaume Laforge shares the list of <a href="https://glaforge.dev/posts/2014/06/20/groovy-related-talks-at-javaone-2014/">Groovy related talks at JavaOne</a></li>
<li>Only one week left till the <a href="https://twitter.com/gr8confus/status/481089948951781377">early bird pricing end of GR8Conf US</a>. Be fast!</li>
<li><a href="http://springone2gx.com/">Super Early Bird registration for SpringOne2GX</a> ends on June 30th</li>
<li>JDriven is organizing a <a href="http://www.jdriven.com/seminars#grails">workshop on building REST-ful apps with Grails</a>, on July 2nd (Dutch)</li>
<li>Russ Danner will give a webinar on: <a href="https://spring.io/blog/2014/06/12/webinar-conquering-content-enabled-web-and-mobile-applications-with-spring-and-groovy">conquering content-enabled web and mobile applications with Spring and Groovy</a> on July 29th</li>
</ul>
<img src="https://pixstat-adh43sr7ba-ew.a.run.app/transparent.gif" width="1" height="1" />]]></content:encoded></item><item><title>Groovy related talks at JavaOne 2014</title><link>https://glaforge.dev/posts/2014/06/20/groovy-related-talks-at-javaone-2014/</link><pubDate>Fri, 20 Jun 2014 00:00:00 +0200</pubDate><guid>https://glaforge.dev/posts/2014/06/20/groovy-related-talks-at-javaone-2014/</guid><description>&lt;p>Oracle is publishing the agenda of the upcoming JavaOne 2014 conference. And I&amp;rsquo;d like to highlight the &lt;a href="https://oracleus.activeevents.com/2014/connect/search.ww?eventRef=javaone#loadSearch-event=null&amp;amp;searchPhrase=groovy&amp;amp;searchType=session&amp;amp;tc=0&amp;amp;sortBy=&amp;amp;p=&amp;amp;i(10009)=10111">Groovy presentations&lt;/a> I&amp;rsquo;ve noticed that you might be attending in following if you are in San Francisco:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=5839">Groovy in the Light of Java 8 [CON5839]&lt;/a> With Java 8 out the door, Java developers can at last benefit from the long-awaited lambdas to taste the newly found functional flavor of the language. Streams are there to work more easily and efficiently with heaps of data. Those things are not new to developers acquainted with Groovy. But what is left to Groovy to make it attractive beyond all the aspects in which Java has caught up with Groovy? In this session, a Groovy project lead shows you how Groovy is still relevant in the JVM landscape, how similar or different Java and Groovy can be, how Groovy further improves the developer experience on top of JDK 8, and what Groovy offers beyond Java.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=5996">Groovy in 2014 and Beyond [CON5996]&lt;/a> With three million downloads in a year, Groovy still clearly leads the pack of alternative languages on the JVM, but it’s not resting on its laurels. The latest Groovy release, 2.3, is chock-full of useful new features and performance improvements. In particular, Groovy now supports the concept of “traits” for elegantly composing behaviors. Its JSON support is now the most performant of all the JSON libraries available to date. Groovy 2.3 introduces a new markup-based template engine, new code transformations, and much more. In this session, a Groovy project lead guides you through the latest advancements in the Groovy programming language and tells you what’s cooking for the next releases.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1768">Exploring Groovy Metaprogramming [CON1768]&lt;/a> One of the key benefits of Groovy is its metaprogramming capability. The built-in features of the language enable you to realize AOP without the need for any heavyweight tools. This presentation takes an in-depth look at the metaprogramming capabilities of Groovy and explains when to use mixins, method injection, and method synthesis.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=3538">Functional Programmning the Groovy Way [CON3538]&lt;/a> In recent years, functional programming has gained ground over object-oriented programming, mainly due to the advancement in computing power. The JVM is no exception. You can find powerful contenders in Clojure and Scala, but Groovy is not that far back in the race. The Groovy programming language contains a wide array of APIs and features that facilitate a functional programming style, such as closure composition, memorization, trampolines, and iterator methods. This session explores all of these features that are sure to spice up your daily experience.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=2425">Rethinking API Design with Groovy Traits [CON2425]&lt;/a> Groovy 2.3 introduces the concept of traits in the language. Traits look like interfaces but enable the developer to add both implementation and state. They introduce multiple inheritance in the language while avoiding the diamond problem. Traits will let you rethink the way you design APIs in Groovy, by favoring composition of behaviors. This session explains what traits are, how they were implemented in Groovy, and what they have to offer to make your code more readable and maintainable.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1764">Groovy and Grails Puzzlers: As Usual—Traps, Pitfalls, and End Cases [CON1764]&lt;/a> Remember the epic Java puzzlers? Here’s the Groovy version, and there are some neat ones! Even though it is totally a Grails shop, some of these had JFrog people scratching their head for days, trying to figure them out. And there is more! Contributions from the truly Groovy senseis, including @glaforge, @aalmiray, @tim_yates, and @kenkousen, make this session an unforgettable journey to Groovy’s O_O. You’ll get the expected dose of fun and enlightenment while hearing about mistakes and failures, great and small, in hard-core Groovy/Grails development.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1769">Applying Groovy Closures for Fun and Productivity [CON1769]&lt;/a> You can program higher-order functions in Groovy quite easily by using closures, but the benefits of closures go far beyond that. Groovy has a variety of capabilities hidden in closures. This presentation unlocks that treasure and explores ways in which we can design applications by using Groovy closures to apply different design patterns, to create fluent interfaces, and even to program asynchrony.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1703">HTML5/AngularJS/Groovy/Java and MongoDB Together: What Could Possibly Go Wrong? [CON1703]&lt;/a> We hear it’s common to create a minimum viable product (MVP) in a language that facilitates rapid prototyping and then migrate to the JVM when the application requires better stability and performance. In this session, the speaker uses Java to create a web application in less than hour. The JVM is a polyglot platform, and you’ll learn how to use the correct tools for this application, including AngularJS, Bootstrap, HTML5, web services, Java, MongoDB, and Groovy—it’s fully buzzword-compliant. The presentation doesn’t go into every technology in depth but demonstrates the role of each tool and how the tools interact. The session will result in a fully working mobile/browser-friendly app without compromise of design or good practice. It’s even going to have tests.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1752">Plugging Users In: Extend Your Application with Pluggable Groovy DSL [CON1752]&lt;/a> It is often beneficial to enable users to extend your software with their own logic, and with dynamic languages on the JVM, it is also easy to do so. This session shares JFrog’s experience in creating a public, Groovy-authored user plug-in interface. It explains what domain-specific languages (DSLs) are, what their relevance to user plug-ins is, and how they can be implemented in Groovy or Java. It also discusses another very important aspect of pluggability: good public API design. Further, it covers security concerns and how they should be tackled. And finally, it discusses classpath isolation issues you may run into and compares different solutions to this problem.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=2939">Script Bowl 2014: The Battle Rages On [CON2939]&lt;/a> In this contest, languages that run on the JVM, represented by their respective language nerds, battle for bragging rights as the most popular language by showing off their shiny new features. Adding a twist to the format in which the audience members will pick this year’s winner(s): they will also vote on a language that should not return in 2015. Returning from 2013 are language gurus representing Clojure, Groovy, JRuby, and Scala. Attend this fun-filled and technically invigorating session to judge which scripting language best meets your needs. You’ll also be able to compare the respective languages and spark some thought-provoking discussions with the panelists that will be beneficial to the entire Java community.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=3693">Spring 4TW! [CON3693]&lt;/a> Spring 4’s here, and with it comes a bevy of new features designed to support modern web developers as well as enrich the component model and enable even-more-adaptive enterprise applications. Spring 4 also serves as the underpinning of Spring Boot, the entry point into the Spring platform. In this session, a Spring developer advocate discusses Spring 4’s new support for WebSocket (including extra support for STOMP), conditional configuration, dependency injection, Java 8 and Java EE 7 support (JCache support, the Batch JSR, ManagedExecutorService support, and so on), the Groovy language, and more. The presentation also looks at Spring Boot as a productive entry point into the Spring platform.&lt;/li>
&lt;li>&lt;a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=7902">Writing Highly Concurrent Polyglot Applications with Vert.x [CON7902]&lt;/a> Vert.x is a lightweight, high-performance, polyglot, asynchronous application platform for the JVM. This session covers the design principles and motivation behind Vert.x, including its concurrency model, and discusses why Vert.x is a great fit for super-simple, highly concurrent applications. It also dives into some of the key Vert.x features, including the distributed event bus and high availability, and discusses some of the up-and-coming new features in the latest development branch for Vert.x 3.0, including distributed map support, management, and monitoring. The presentation includes demonstrations involving examples in several JVM languages such as Java, JavaScript, Scala, and Groovy.&lt;/li>
&lt;/ul>
&lt;p>Update: I only searched sessions with the &amp;ldquo;groovy&amp;rdquo; keyword, but other Groovy ecosystem projects are also present, like Griffon, Gradle or Spock! So let me fix that by appending some more Groovy-related sessions to my list!&lt;/p></description><content:encoded>
<![CDATA[<p>Oracle is publishing the agenda of the upcoming JavaOne 2014 conference. And I&rsquo;d like to highlight the <a href="https://oracleus.activeevents.com/2014/connect/search.ww?eventRef=javaone#loadSearch-event=null&amp;searchPhrase=groovy&amp;searchType=session&amp;tc=0&amp;sortBy=&amp;p=&amp;i(10009)=10111">Groovy presentations</a> I&rsquo;ve noticed that you might be attending in following if you are in San Francisco:</p>
<ul>
<li><a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=5839">Groovy in the Light of Java 8 [CON5839]</a> With Java 8 out the door, Java developers can at last benefit from the long-awaited lambdas to taste the newly found functional flavor of the language. Streams are there to work more easily and efficiently with heaps of data. Those things are not new to developers acquainted with Groovy. But what is left to Groovy to make it attractive beyond all the aspects in which Java has caught up with Groovy? In this session, a Groovy project lead shows you how Groovy is still relevant in the JVM landscape, how similar or different Java and Groovy can be, how Groovy further improves the developer experience on top of JDK 8, and what Groovy offers beyond Java.</li>
<li><a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=5996">Groovy in 2014 and Beyond [CON5996]</a> With three million downloads in a year, Groovy still clearly leads the pack of alternative languages on the JVM, but it’s not resting on its laurels. The latest Groovy release, 2.3, is chock-full of useful new features and performance improvements. In particular, Groovy now supports the concept of “traits” for elegantly composing behaviors. Its JSON support is now the most performant of all the JSON libraries available to date. Groovy 2.3 introduces a new markup-based template engine, new code transformations, and much more. In this session, a Groovy project lead guides you through the latest advancements in the Groovy programming language and tells you what’s cooking for the next releases.</li>
<li><a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1768">Exploring Groovy Metaprogramming [CON1768]</a> One of the key benefits of Groovy is its metaprogramming capability. The built-in features of the language enable you to realize AOP without the need for any heavyweight tools. This presentation takes an in-depth look at the metaprogramming capabilities of Groovy and explains when to use mixins, method injection, and method synthesis.</li>
<li><a href="https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=3538">Functional Programmning the Groovy Way [CON3538]</a> In recen