<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tomas Fernandez</title>
    <description>The latest articles on Forem by Tomas Fernandez (@tomfern).</description>
    <link>https://forem.com/tomfern</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F246163%2Fd3e35cfd-7503-407d-a461-cccb8657c26f.jpg</url>
      <title>Forem: Tomas Fernandez</title>
      <link>https://forem.com/tomfern</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tomfern"/>
    <language>en</language>
    <item>
      <title>What is MCP Really?</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Mon, 03 Nov 2025 13:02:00 +0000</pubDate>
      <link>https://forem.com/semaphore/what-is-mcp-really-545l</link>
      <guid>https://forem.com/semaphore/what-is-mcp-really-545l</guid>
      <description>&lt;p&gt;Photo by Pavel Danilyuk: &lt;a href="https://www.pexels.com/photo/a-robot-holding-a-wine-8439094/" rel="noopener noreferrer"&gt;https://www.pexels.com/photo/a-robot-holding-a-wine-8439094/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; has been described as “the USB-C for AI”. It’s a fitting analogy, but what does it &lt;em&gt;really&lt;/em&gt; mean? What is MCP?&lt;/p&gt;

&lt;p&gt;Large language models (LLMs) are incredibly capable, but they only know what they know. Once trained, an LLM can’t access real-time information or specialized systems until it connects to external tools.&lt;/p&gt;

&lt;p&gt;MCP provides a &lt;strong&gt;communication protocol&lt;/strong&gt; that lets models like GPT or Claude interact with any compatible tool or service. Instead of relying on proprietary APIs or one-off integrations, MCP introduces a &lt;strong&gt;shared language for interaction&lt;/strong&gt; between AIs (as clients) and software (as servers).&lt;/p&gt;

&lt;h2&gt;
  
  
  How the MCP Works
&lt;/h2&gt;

&lt;p&gt;At its core, MCP is a simple &lt;strong&gt;client–server model&lt;/strong&gt;. The large language model acts as the &lt;em&gt;client&lt;/em&gt;, while a &lt;em&gt;server&lt;/em&gt; provides one or more tools the AI can use. Communication between the two happens through JSON-RPC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frysfuxy5noah5xs2keln.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frysfuxy5noah5xs2keln.jpeg" alt="Communication Protocol" width="800" height="1072"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During initialization, the AI and server negotiate capabilities. Then the AI sends a client a &lt;code&gt;tools/list&lt;/code&gt; request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tools/list"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cursor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"optional-cursor-value"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the server responds with a manifest of available tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get current weather information for a location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"City name or zip code"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nextCursor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next-page-cursor"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI now knows what tools or functions are available and picks one for a user request. So, in our example, the AI would call the &lt;code&gt;get_weather&lt;/code&gt; tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lisbon"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the MCP server responds with structured output, in this case, how's the weather in Lisbon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Current weather in Lisbon:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Temperature: 32°C&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Conditions: Partly cloudy"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Local vs Remote MCP Servers
&lt;/h2&gt;

&lt;p&gt;The easiest way to implement the MCP server is to run it on the same host as the client. For example, if I'm using OpenAI Codex or Claude Desktop, the AI can spawn an MCP server locally and communicate over &lt;strong&gt;standard input and output&lt;/strong&gt; (stdio).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4y38u78vqvjilme2rog.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4y38u78vqvjilme2rog.jpeg" alt="STDIO Mode" width="800" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more complex setups, the MCP allows communication over HTTP and provides mechanisms for &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#sequence-diagram" rel="noopener noreferrer"&gt;authentication and authorization&lt;/a&gt;. These servers can require credentials, API keys, or tokens, depending on how sensitive their capabilities are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The State of the Standard
&lt;/h2&gt;

&lt;p&gt;MCP is still an emerging standard. It was introduced in 2024 as an &lt;strong&gt;open specification&lt;/strong&gt;. MCP is being developed collaboratively by several players in the AI ecosystem.&lt;/p&gt;

&lt;p&gt;The initial specification was published at &lt;a href="https://modelcontextprotocol.info" rel="noopener noreferrer"&gt;modelcontextprotocol.info&lt;/a&gt;, and the work is happening in the open with input from AI companies, open-source developers, and infrastructure providers.&lt;/p&gt;

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

&lt;p&gt;MCP represents a quiet but fundamental shift in how AI systems interact with the world.&lt;br&gt;&lt;br&gt;
It offers a &lt;strong&gt;shared, open standard&lt;/strong&gt;; a common language that any model and any tool can use to talk to each other.&lt;/p&gt;

&lt;p&gt;For developers, this means fewer one-off connectors and more reusable, interoperable systems. For users, it means AI assistants that can reach beyond their training data and tap into live information, files, or applications with precision and context.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy building!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Lightweight Containers With Docker and WebAssembly</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Mon, 18 Dec 2023 14:14:22 +0000</pubDate>
      <link>https://forem.com/semaphore/lightweight-containers-with-docker-and-webassembly-19fl</link>
      <guid>https://forem.com/semaphore/lightweight-containers-with-docker-and-webassembly-19fl</guid>
      <description>&lt;p&gt;🐳Did you know you can now build and run WebAssembly with Docker Desktop? That's the only tool you need to run the same binary file at native speed on any platform: Linux, Windows, Mac or any browser.&lt;/p&gt;

&lt;p&gt;WebAssembly, or Wasm, is increasingly relevant in software development. It's a portable binary code format designed for efficient and fast execution on any platform, including web browsers. &lt;/p&gt;

&lt;p&gt;Watch the hands-on tutorial:&lt;/p&gt;

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

&lt;p&gt;WebAssembly is so essential for the web that Solomon Hykes, the founder of Docker, announced that if Wasm and WASI were available in 2008, Docker might not have been developed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fc115766b5cd3cf91f855325b4e38db7dd6f18314990ea07682b409ed60599420.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fc115766b5cd3cf91f855325b4e38db7dd6f18314990ea07682b409ed60599420.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite its advantages, WebAssembly has yet to reach the same level of adoption as Docker. Part of the challenge lies in the complexity of the tooling for Wasm, particularly in building, running, and debugging across different languages. For example, creating a Wasm binary involves installing a language-specific compiler toolchain. &lt;/p&gt;

&lt;p&gt;Docker offers a solution here, providing a reproducible and isolated build environment. Let's see how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker and WebAssembly
&lt;/h2&gt;

&lt;p&gt;In July 2023, Docker introduced experimental support for WebAssembly, adding a new dimension to running Wasm applications. This integration brings several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Process&lt;/strong&gt;: Using Docker for building and running Wasm applications reduces the learning curve by minimizing the required tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Portability&lt;/strong&gt;: Wasm containers don't require different builds for different machine architectures, simplifying deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Builds&lt;/strong&gt;: Docker's isolated environment ensures consistent builds across various platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with Existing Tools&lt;/strong&gt;: Docker's compatibility with Docker Compose and Kubernetes facilitates complex deployments and scaling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2F15414c20da20bbd6f3d6828e6b813719b0de1ab72eb134ac688f2a3d1584c16a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2F15414c20da20bbd6f3d6828e6b813719b0de1ab72eb134ac688f2a3d1584c16a.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Wasm container
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a new project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;cargo new docker-wasm-demo-rust


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

&lt;/div&gt;

&lt;p&gt;The default project is a "Hello, World" application printing to console, perfect as a first step. &lt;/p&gt;

&lt;p&gt;To build and run it, we use cargo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;cargo run
Hello World!


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

&lt;/div&gt;

&lt;p&gt;But this is a native binary, not WebAssembly. To compile a Wasm target, let's install the WebAssembly toolchain. This command requires that you have installed &lt;a href="https://www.rust-lang.org/tools/install" rel="noopener noreferrer"&gt;rustup&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;rustup target add wasm32-wasi


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

&lt;/div&gt;

&lt;p&gt;Next, we can build a Wasm target with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;cargo build &lt;span class="nt"&gt;--target&lt;/span&gt; wasm32-wasi &lt;span class="nt"&gt;--release&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We can't run this directly from the command line unless we install some runtime like &lt;a href="https://wasmtime.dev/" rel="noopener noreferrer"&gt;wasmtime&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;was &lt;span class="nb"&gt;time &lt;/span&gt;target/wasm32-wasi/release/docker-wasm-demo-rust.wasm
Hello World!


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

&lt;/div&gt;

&lt;p&gt;This works, but there's a lot of tooling involved. We need the Rust compilers, the Wasm toolchain, and some runtime to test it. We can streamline all this with Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Wasm in Docker
&lt;/h2&gt;

&lt;p&gt;Before using Docker with WebAssembly, we need to ensure we have the latest version of Docker Desktop installed and then enable containerd and Wasm support in the options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2F9fa02598b3a277ddafe6050e82940f29071088c2e97d51d497b06a2ec854348d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2F9fa02598b3a277ddafe6050e82940f29071088c2e97d51d497b06a2ec854348d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we need to create a simple Dockerfile that copies the wasm inside the container:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/wasm32-wasi/release/docker-wasm-demo-rust.wasm /hello.wasm&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "/hello.wasm" ]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We need to build the image, but we need to switch to the &lt;code&gt;wasi/wasm&lt;/code&gt; platform for this case.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker build &lt;span class="nt"&gt;--platform&lt;/span&gt; wasi/wasm &lt;span class="nt"&gt;-t&lt;/span&gt; hello-wasm &lt;span class="nb"&gt;.&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If we check the Docker Desktop image tab, we should see the new image with the WASM badge. This means the image is actually WebAssembly instead of a regular container:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fc36b02d453eeed6aee73c5082742370db711eb870870e8a6619fbc23f3f8cc37.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fc36b02d453eeed6aee73c5082742370db711eb870870e8a6619fbc23f3f8cc37.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To run the image, we use the familiar &lt;code&gt;docker run&lt;/code&gt; with two extra flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--runtime=io.containerd.wasmedge.v1&lt;/code&gt; one of the possible runtimes supported by Docker Desktop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--platform=wasi/wasm&lt;/code&gt; to tell Docker we want to run the Wasm image. Otherwise, Docker will fail to find the image.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker run &lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;io.containerd.wasmedge.v1 &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wasi/wasm hello-wasm


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Building Wasm with Docker
&lt;/h2&gt;

&lt;p&gt;We can step the process further by using Docker to build the image. This allows us to run the build in a clean, shareable environment, making it easier to run the build stage in CI/CD.&lt;/p&gt;

&lt;p&gt;The following 2-stage Dockerfile builds and creates the Wasm image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first stage uses the Rust base image to build the Wasm inside the container&lt;/li&gt;
&lt;li&gt;The second stage copies the Wasm binary from the first stage and creates the Wasm image.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;--platform=$BUILDPLATFORM rust:1.74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;rustup target add wasm32-wasi
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /build
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Cargo.toml .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;cargo build &lt;span class="nt"&gt;--target&lt;/span&gt; wasm32-wasi &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;a+x /build/target/wasm32-wasi/release/docker-wasm-demo-rust.wasm

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link --from=build /build/target/wasm32-wasi/release/docker-wasm-demo-rust.wasm /hello.wasm&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "/hello.wasm" ]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Let's build the image again:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;--platform&lt;/span&gt; wasi/wasm &lt;span class="nt"&gt;-t&lt;/span&gt; hello-wasm &lt;span class="nb"&gt;.&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And then run it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;io.containerd.wasmedge.v1 &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wasi/wasm hello-wasm


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

&lt;/div&gt;

&lt;p&gt;As you can see, this new Docker feature lets us work with Wasm without needing anything other than Docker Desktop on our machines. Neat!&lt;/p&gt;

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

&lt;p&gt;WebAssembly and Docker do make an interesting combination. At the end of the day, having more options for developers, it's always good news. Be sure to check the YouTube tutorial video above for a more in-depth explanation.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy building!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>containers</category>
      <category>webassembly</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>State of DevOps 2023 Report: Key Findings and Insights</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Thu, 14 Dec 2023 17:39:36 +0000</pubDate>
      <link>https://forem.com/semaphore/state-of-devops-2023-report-key-findings-and-insights-hl</link>
      <guid>https://forem.com/semaphore/state-of-devops-2023-report-key-findings-and-insights-hl</guid>
      <description>&lt;p&gt;The much-anticipated State of DevOps 2023 report is finally out. Developer by the &lt;a href="https://dora.dev/"&gt;DORA team at Google&lt;/a&gt;, this annual report is a synthesis of trends and insights collected from professionals and organizations working in the realm of DevOps. &lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ramifications of AI on productivity&lt;/li&gt;
&lt;li&gt;What factors contribute to job satisfaction and burnout.&lt;/li&gt;
&lt;li&gt;What matters when choosing a cloud.&lt;/li&gt;
&lt;li&gt;The non-linear nature of investing in reliability and SRE practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've published a video covering all the key insights. Scroll down to see a summary.&lt;/p&gt;

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

&lt;p&gt;Here’s a summarized look at some of the major takeaways. You can download the report for free from Google:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ShGR6n4q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/be1c0af77bc3c4f271c0b67da6d3140c0a91a10da1c190944123dd6d11e51dec.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ShGR6n4q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/be1c0af77bc3c4f271c0b67da6d3140c0a91a10da1c190944123dd6d11e51dec.jpeg" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📕 &lt;a href="https://cloud.google.com/devops/state-of-devops/"&gt;2023 State of DevOps Report&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key findings
&lt;/h2&gt;

&lt;p&gt;From the outset, the report presented several new areas of focus. On page 4, the spotlight was on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pivotal role documentation plays in the ecosystem.&lt;/li&gt;
&lt;li&gt;How underrepresented groups are at a higher risk of burnout.&lt;/li&gt;
&lt;li&gt;The growing emphasis on prioritizing user feedback and needs.&lt;/li&gt;
&lt;li&gt;The role of the cloud in boosting productivity and job satisfaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kBICin1E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/c1d218dc27680ace75bb027736dace6b687b8fb59eabeedf6d06337430827467.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kBICin1E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/c1d218dc27680ace75bb027736dace6b687b8fb59eabeedf6d06337430827467.png" alt="" width="800" height="1035"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Page 6 of the report outlines key organizational outcomes like enhancing productivity and job satisfaction while curbing burnout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CI8c1CjJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/95ed8ddc567f61267ec63e6e14a58c91d63b3bbdfd0214b25a11630e99721887.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CI8c1CjJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/95ed8ddc567f61267ec63e6e14a58c91d63b3bbdfd0214b25a11630e99721887.png" alt="" width="800" height="1035"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritizing Users
&lt;/h2&gt;

&lt;p&gt;One of the standout findings is how tuning into users during the developmental stages can amplify performance metrics. Wether they are customers, developers, analysts or testers, when we focus on the needs of our users there is a substantial increase in performance and reliability across the board.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NaRj75NR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/35ab6871899600551837fa19bdc99cd7e19ee06f786067acd8015998473ed265.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NaRj75NR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/35ab6871899600551837fa19bdc99cd7e19ee06f786067acd8015998473ed265.jpg" alt="" width="543" height="874"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Taking a user-centric approach reaps multifold benefits. It not only fosters an environment conducive to enhanced performance, CI/CD, job satisfaction, and productivity, but also plays a role in mitigating burnout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical capabilities that matter
&lt;/h2&gt;

&lt;p&gt;New technologies call for a refreshed set of metrics. The results show that the impact of AI on team performance was less pronounced than expected. Instead, more foundational capabilities like CI/CD, robust documentation, trunk-based development, and rapid code reviews emerged as stronger pillars of performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ojCR_qhm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/c1692144f7cf2028599b1799a8c8a05ffd7076f48db0ccf0e917563e38f79a54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ojCR_qhm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/c1692144f7cf2028599b1799a8c8a05ffd7076f48db0ccf0e917563e38f79a54.png" alt="" width="800" height="1035"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The power of flexibility
&lt;/h2&gt;

&lt;p&gt;The flexibility of infrastructure directly correlates with performance, with most organizations turning to the cloud for this adaptability. However, the type of cloud chosen holds significance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private clouds improve operational and team performance.&lt;/li&gt;
&lt;li&gt;Public clouds enhance organizational and team performance at the expense of operational and software delivery effectiveness. This dip in performance may be due teams shifting to the cloud and encountering unfamiliar tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q_0Vdmtl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/750591d156ebe5eb5afb009280c8d2fa51b521de1253f10c2d6c4f184079ee38.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q_0Vdmtl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/750591d156ebe5eb5afb009280c8d2fa51b521de1253f10c2d6c4f184079ee38.jpg" alt="" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation: an amplifier of technical capabilities
&lt;/h2&gt;

&lt;p&gt;State of DevOps 2023 underscored the immense value of quality documentation. Beyond being foundational, it magnifies other technical capabilities, with the potential to boost performance by nearly 13x when paired with trunk-based development. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eP3kD3aT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/487003797011057e01e533f674c5acaec22b256f976c41bde5d7157ca6f7bf6f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eP3kD3aT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/487003797011057e01e533f674c5acaec22b256f976c41bde5d7157ca6f7bf6f.jpg" alt="" width="536" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, well-maintained documentation also positively influences the human side of development, elevating job satisfaction and productivity and reducing burnout tendencies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CuJY051F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e8a8c84c48c020eccef469661df13386c6c9f305d593b110f2661781978a3ae9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CuJY051F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e8a8c84c48c020eccef469661df13386c6c9f305d593b110f2661781978a3ae9.jpg" alt="" width="548" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cultivating a Generative Culture
&lt;/h2&gt;

&lt;p&gt;The idea of a generative culture has been a recurring theme in the State of DevOps reports. Rooted in the studies of American sociologist Ron Westrum, a generative culture paves the way for innovation, higher productivity, and a more fulfilling work environment. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qpgOVv0L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/9bfe419a53c003b4d311db6fbbbc22aaafe65968f7f94070ec64a0cdef784862.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qpgOVv0L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/9bfe419a53c003b4d311db6fbbbc22aaafe65968f7f94070ec64a0cdef784862.png" alt="" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data confirms that a generative culture is a robust indicator of high performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dC7D5ZVx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/0633d03325a18d9703904b25768dd9b41580af06b5cc416201ae68158c678f86.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dC7D5ZVx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/0633d03325a18d9703904b25768dd9b41580af06b5cc416201ae68158c678f86.jpg" alt="" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;I have only scratched the surface on the insights that State of DevOps 2023 reveals. I highly recommend &lt;a href="https://cloud.google.com/devops/state-of-devops/"&gt;downloading the report&lt;/a&gt; and checking it our by yourself.&lt;/p&gt;

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

</description>
      <category>devops</category>
      <category>analytics</category>
      <category>productivity</category>
      <category>devex</category>
    </item>
    <item>
      <title>Function Calling: Integrate Your GPT Chatbot With Anything</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Wed, 09 Aug 2023 14:02:14 +0000</pubDate>
      <link>https://forem.com/semaphore/function-calling-integrate-your-gpt-chatbot-with-anything-3l5d</link>
      <guid>https://forem.com/semaphore/function-calling-integrate-your-gpt-chatbot-with-anything-3l5d</guid>
      <description>&lt;p&gt;&lt;em&gt;Yes we do!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine creating an AI assistant to which you can say something like, "Book me the earliest reservation for the nearest Thai restaurant and update my calendar." Language models continue to push boundaries and evolve. OpenAI, the company behind ChatGPT, recently introduced a powerful new feature called &lt;a href="https://openai.com/blog/function-calling-and-other-api-updates"&gt;function calling&lt;/a&gt; in their GPT models. Function calling simplifies the creation of chatbots that communicate with external tools and APIs, opening up a new realm of possibilities for AI-powered applications.&lt;/p&gt;

&lt;p&gt;In this article, we will delve into the concept of function calling, its implications, and its transformative impact on how we interact with AI systems by creating &lt;strong&gt;NewsGPT&lt;/strong&gt;, a chatbot that brings you breaking news worldwide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8_dKoa-X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/1a84c9b54df40126bedc83e448c159af74579b9bb050c18541e610747643afbc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8_dKoa-X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/1a84c9b54df40126bedc83e448c159af74579b9bb050c18541e610747643afbc.jpg" alt="Example run of NewsGPT. A command line interface shows the user asking, &amp;quot;What's the latest news in the US?&amp;quot; and receiving a list of five articles with links to the journals or sources." width="800" height="684"&gt;&lt;/a&gt;&lt;br&gt;
Example run of NewsGPT. A command line interface shows the user asking, "What's the latest news in the US?" and receiving a list of five articles with links to the journals or sources.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Function Calling?
&lt;/h2&gt;

&lt;p&gt;Function calling is a new feature in OpenAI's &lt;strong&gt;GPT-4-0613&lt;/strong&gt; and &lt;strong&gt;GPT-3.5 Turbo-0613&lt;/strong&gt; models. These AI models are trained to detect the need for function calling based on the user's prompt and respond with a structured call request instead of regular text.&lt;/p&gt;

&lt;p&gt;Function calling allows chatbots to interact with other systems, enabling the GPT models to respond to questions they otherwise could not, such as those requiring real-time information or data not included in their training set. In other words, function calling provides another way to &lt;strong&gt;teach AI models how to interact with the external world&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is the Purpose of Function Calling?
&lt;/h3&gt;

&lt;p&gt;Before function calling, there were only two ways of augmenting the capabilities of a GPT language model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning&lt;/strong&gt;: further training the language model by providing example responses. &lt;a href="https://platform.openai.com/docs/guides/fine-tuning"&gt;Fine-tuning&lt;/a&gt; is a powerful technique, but it requires significant work (and cost) to prepare the training data. In addition, only a few older models can be fine-tuned until OpenAI enables this feature in GPT-3.5 and GPT-4 models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings&lt;/strong&gt;: &lt;a href="https://semaphoreci.com/blog/word-embeddings"&gt;enriching the prompt with context data&lt;/a&gt; can extend the bot's knowledge and create more accurate responses. The downside is that this context can take up a lot of tokens, increasing the cost and leaving fewer tokens free for building complex responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Function calling adds a third way of extending the GPT capabilities by allowing it to ask us to run functions on its behalf. The model can then take the function's result and build a human-readable response that fits seamlessly into the current conversation.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Use Function Calling
&lt;/h2&gt;

&lt;p&gt;The introduction of function calling changes how we interact with the GPT API. Before these functions, the interaction was simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a prompt to the API.&lt;/li&gt;
&lt;li&gt;Receive a response.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bMAfCyJZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/ad354bd337fa39a4d6aa6f6899d24a4880a41cb5958c8bb4935602ed0a453cc5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bMAfCyJZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/ad354bd337fa39a4d6aa6f6899d24a4880a41cb5958c8bb4935602ed0a453cc5.jpg" alt="A diagram showing the basic interaction with the GPT API before function calling." width="728" height="418"&gt;&lt;/a&gt;&lt;br&gt;
A diagram showing the basic interaction with the GPT API before function calling.&lt;/p&gt;

&lt;p&gt;With function calling, the sequence becomes more involved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send the user prompt along with a list of callable functions.&lt;/li&gt;
&lt;li&gt;The GPT model responds with either a regular text response or a function call request.&lt;/li&gt;
&lt;li&gt;If the model requests a function call, your chatbot's job is to execute it and return the results to the API.&lt;/li&gt;
&lt;li&gt;Using the supplied data, the model then forms a coherent text response. However, in some cases, the API may request a new function call.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--03ckVtyB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/8f47ec7267d9c2784975b1a5b5d6e7ee224d9939201c7299ee92521c54eca8f2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--03ckVtyB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/8f47ec7267d9c2784975b1a5b5d6e7ee224d9939201c7299ee92521c54eca8f2.jpg" alt="A diagram showing the more complex interaction with the GPT API using function calling." width="728" height="628"&gt;&lt;/a&gt;&lt;br&gt;
A diagram showing the more complex interaction with the GPT API using function calling.&lt;/p&gt;
&lt;h3&gt;
  
  
  Function Calling with the Chat Completions API
&lt;/h3&gt;

&lt;p&gt;To allow the model to call functions, we must use the &lt;a href="https://platform.openai.com/docs/guides/gpt/chat-completions-api"&gt;Chat Completions API&lt;/a&gt;. The API takes a POST request with a JSON payload containing a list of messages to process. A typical prompt sent to the API looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How many planets does the solar system have?"&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;role: user&lt;/code&gt; informs the API that the &lt;code&gt;content&lt;/code&gt; is user-generated. The GPT API might reply with something along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chatcmpl-7WVo3fYwerpAptzeqU46JamOvgBzh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1687983115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo-0613"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"There are eight planets in the solar system. They are:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;1. Mercury&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;2. Venus&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;3. Earth&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;4. Mars&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;5. Jupiter&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;6. Saturn&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;7. Uranus&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;8. Neptune"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;role: assistant&lt;/code&gt; corresponds to messages generated by the GPT model. To keep the conversation flow, we must supply the entire message history back to the API on each request. For example, if we want to delve deeper into our previous question, the corresponding JSON payload would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"How many planets does the solar system have?"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"There are eight planets in the solar system. They are:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;1. Mercury&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;2. Venus&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;3. Earth&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;4. Mars&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;5. Jupiter&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;6. Saturn&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;7. Uranus&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;8. Neptune"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Tell me more about the second planet."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To let the language model know it can call functions, we need to add a list of them to the payload. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo-0613"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"How is the weather in NYC?"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"functions"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"get_current_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Get the current weather in a given location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The city and state, e.g. San Francisco, CA"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have noticed that we switched the model to "gpt-3.5-turbo-0613" because it supports function calling. If the model decides to call the function, we will receive a response of type &lt;code&gt;role: assistant&lt;/code&gt; with a &lt;code&gt;function_call&lt;/code&gt; property defined like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chatcmpl-7WWG94C1DCFlAk5xmUwrZ9OOhFnOq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1687984857&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo-0613"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"function_call"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"get_current_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;New York, NY&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"function_call"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our task is to execute &lt;code&gt;get_current_weather&lt;/code&gt; with the provided arguments. OpenAI &lt;strong&gt;does not&lt;/strong&gt; execute the function. Instead, it's the job of our chatbot to run it and parse the returned data.&lt;/p&gt;

&lt;p&gt;Once we retrieve the weather data, we send it back to the model using a new type of role called &lt;code&gt;function&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo-0613"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"How is the weather in NYC?"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"function_call"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"get_current_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;New York, NY&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"get_current_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Temperature: 57F, Condition: Raining"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"functions"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"get_current_weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Get the current weather in a given location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The city and state, e.g. San Francisco, CA"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we passed the entire message history to the API, including our original prompt, the function call from the model, and the result of executing the weather function in our code. This enables the language model to understand the context in which the function was called.&lt;/p&gt;

&lt;p&gt;Finally, the model may reply with a properly formatted answer, responding to our initial question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chatcmpl-7WWQUccvLUfjhbIcuvFrj2MDJVEiN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1687985498&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo-0613"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The weather in New York City is currently raining with a temperature of 57 degrees Fahrenheit."&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building NewsGPT
&lt;/h2&gt;

&lt;p&gt;To learn more about function calling, we will build &lt;strong&gt;NewsGPT&lt;/strong&gt;, a Python chatbot capable of accessing breaking news in real time. The bot will use function calling to determine what kind of data to fetch from &lt;a href="https://newsapi.org"&gt;NewsAPI.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To build the bot, you will need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An OpenAI API key. This requires a credit card because API requests have a cost. However, new accounts receive \$5 credit for the first three months.&lt;/li&gt;
&lt;li&gt;A NewsAPI API key. Register at &lt;a href="https://newsapi.org/"&gt;NewsAPI.org&lt;/a&gt; and get a starter key for free.&lt;/li&gt;
&lt;li&gt;Python 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting Up the Project
&lt;/h3&gt;

&lt;p&gt;Install the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;openai tiktoken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project consists of only one file; let's name it &lt;code&gt;newsgpt.py&lt;/code&gt;. I will begin by adding all the necessary imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;openai&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tiktoken&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I will define a few constants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The GPT model to use. I will use &lt;code&gt;gpt-3.5-turbo-16k&lt;/code&gt; as it has a 16k token limit, allowing me to process longer conversations with more context.&lt;/li&gt;
&lt;li&gt;The system prompt that instructs the model on its basic purpose.&lt;/li&gt;
&lt;li&gt;The encoding used to count tokens in strings and messages; required to ensure we do not exceed the language model limits.&lt;/li&gt;
&lt;li&gt;The maximum number of functions to call in a chain (more on this later).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;llm_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gpt-3.5-turbo-16k"&lt;/span&gt;
&lt;span class="n"&gt;llm_max_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15500&lt;/span&gt;
&lt;span class="n"&gt;llm_system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are an assistant that provides news and headlines to user requests. Always try to get the lastest breaking stories using the available function calls."&lt;/span&gt;
&lt;span class="n"&gt;encoding_model_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gpt-3.5-turbo-0613"&lt;/span&gt;
&lt;span class="n"&gt;encoding_model_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cl100k_base"&lt;/span&gt;
&lt;span class="n"&gt;function_call_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All OpenAI models have a token limit. If this limit is exceeded, the API will throw an error instead of responding to our request. So, we need a function to count the number of tokens. I will use this function from the official example documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;num_tokens_from_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="s"&gt;"""Returns the number of tokens used by a list of messages."""&lt;/span&gt;
 &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding_for_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding_model_messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding_model_strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="n"&gt;num_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;num_tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
 &lt;span class="n"&gt;num_tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;num_tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
 &lt;span class="n"&gt;num_tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num_tokens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defining a Function to Call
&lt;/h3&gt;

&lt;p&gt;Now, I will define a function to query the &lt;a href="https://newsapi.org"&gt;NewsAPI.org&lt;/a&gt; API to get the breaking news:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_top_headlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="s"&gt;"""Retrieve top headlines from newsapi.org (API key required)"""&lt;/span&gt;

    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://newsapi.org/v2/top-headlines"&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"x-api-key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'NEWS_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"general"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'country'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch from newsapi.org - reference: https://newsapi.org/docs/endpoints/top-headlines
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'ok'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'totalResults'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; articles from newsapi.org"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed with message:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'No articles found'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;To inform GPT about this function, we need to describe using a specific JSON structure. The format is described in the &lt;a href="https://platform.openai.com/docs/guides/gpt/function-calling"&gt;official documentation&lt;/a&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;signature_get_top_headlines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"get_top_headlines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Get top news headlines by country and/or category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
      &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
         &lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Freeform keywords or a phrase to search for."&lt;/span&gt;
         &lt;span class="p"&gt;},&lt;/span&gt;
         &lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"The 2-letter ISO 3166-1 code of the country you want to get headlines for"&lt;/span&gt;
         &lt;span class="p"&gt;},&lt;/span&gt;
         &lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"The category you want to get headlines for"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
               &lt;span class="s"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;"entertainment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;"general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;"health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;"science"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;"sports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;"technology"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;

      &lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the Chat Completions API with Function Calling
&lt;/h3&gt;

&lt;p&gt;Next, I will define the &lt;code&gt;complete&lt;/code&gt; function, which performs several tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adds a system prompt at the end of the messages. This system message helps define the role that the GPT model will fulfill.&lt;/li&gt;
&lt;li&gt;Removes old messages if the total token count exceeds the model's limit.&lt;/li&gt;
&lt;li&gt;Sends the request to the GPT API.&lt;/li&gt;
&lt;li&gt;Removes the system message from the end of the list.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"auto"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="s"&gt;"""Fetch completion from OpenAI's GPT"""&lt;/span&gt;

 &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;llm_system_prompt&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

 &lt;span class="c1"&gt;# delete older completions to keep conversation under token limit
&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;num_tokens_from_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;llm_max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Working...'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;signature_get_top_headlines&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="n"&gt;function_call&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;function_call&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c1"&gt;# remove system message and append response from the LLM
&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c1"&gt;# call functions requested by the model
&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"function_call"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"function_call"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"get_top_headlines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"function_call"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
 &lt;span class="n"&gt;headlines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_top_headlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
 &lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"get_top_headline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;headlines&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To complete the bot, we will create the main loop that continuously prompts the user for input and provides the answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Hi, I'm a NewsGPT, a breaking news AI assistant. I can give you news for most countries over a wide range of categories."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Here are some example prompts:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; - Tell me about the recent science discoveries&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; - What is the latest news in the US?&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; - What has Elon Musk been up to recently?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;What would you like to know? =&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# the LLM can chain function calls, this implements a limit
&lt;/span&gt;    &lt;span class="n"&gt;call_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'role'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;call_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;call_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;function_call_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_call&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# print last message
&lt;/span&gt;    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;==Response==&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;==End of response=="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to define environment variables for the OpenAI and NewsAPI API keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_KEY
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NEWS_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, load the file and run the chatbot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .env
&lt;span class="nv"&gt;$ &lt;/span&gt;python newsgpt.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We can inquire about a specific topic or person. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; What is Elon Musk up these days?
Working...
Processing 1 articles from newsapi.org
Working...


&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nv"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;

Elon Musk has been &lt;span class="k"&gt;in &lt;/span&gt;the news recently regarding a feud with Mark Zuckerberg, the CEO of Meta. According to an article from The Washington Post, Mark Zuckerberg is trying to make himself more relevant to the tech elite, and there seems to be a rivalry between him and Elon Musk. The article discusses their ongoing fight and how Zuckerberg is trying to compete with Musk&lt;span class="s1"&gt;'s online antagonism and offline antics. You can read more about it [here](https://www.washingtonpost.com/technology/2023/06/24/elon-musk-mark-zuckerberg-fight-meta-twitter-rival/).

==End of response==
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot is also capable of recognizing categories, allowing us to request health-related news:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Are there important developments &lt;span class="k"&gt;in &lt;/span&gt;health?
Working...
Processing 1000 articles from newsapi.org
Working...


&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nv"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;

Based on the latest headlines &lt;span class="k"&gt;in &lt;/span&gt;the health category, here are some important developments:

1. &lt;span class="o"&gt;[&lt;/span&gt;A Case of Herpes Zoster Ophthalmicus &lt;span class="k"&gt;in &lt;/span&gt;a Recently Transplanted Renal Patient]&lt;span class="o"&gt;(&lt;/span&gt;https://www.cureus.com/articles/164429-a-case-of-herpes-zoster-ophthalmicus-in-a-recently-transplanted-renal-patient?score_article&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; - This &lt;span class="k"&gt;case&lt;/span&gt; study presents the clinical course of a 51-year-old male &lt;span class="nb"&gt;who &lt;/span&gt;underwent a renal transplant and developed Herpes Zoster Ophthalmicus.

2. &lt;span class="o"&gt;[&lt;/span&gt;Cuáles son los principales consejos a seguir para mejorar el estado de ánimo a base de una buena alimentación]&lt;span class="o"&gt;(&lt;/span&gt;https://news.google.com/rss/articles/CBMilQFodHRwczovL3d3dy5jYW5hbDI2LmNvbS9nZW5lcmFsL2N1YWxlcy1zb24tbG9zLXByaW5jaXBhbGVzLWNvbnNlam9zLWEtc2VndWlyLXBhcmEtbWVqb3Jhci1lbC1lc3RhZG8tZGUtYW5pbW8tYS1iYXNlLWRlLXVuYS1idWVuYS1hbGltZW50YWNpb24tLTM0NTMwM9IBAA?oc&lt;span class="o"&gt;=&lt;/span&gt;5&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;Spanish&lt;span class="o"&gt;)&lt;/span&gt; - This article provides tips on improving mood through a good diet.

3. &lt;span class="o"&gt;[&lt;/span&gt;Как да стопите коремните мазнини за лятото]&lt;span class="o"&gt;(&lt;/span&gt;https://news.google.com/rss/articles/CBMiXmh0dHBzOi8vbS5hei1qZW5hdGEuYmcvYS81LXpkcmF2ZS1pLWtyYXNvdGEvNjM1Mzkta2FrLWRhLXN0b3BpdGUta29yZW1uaXRlLW1hem5pbmktemEtbGlhdG90by_SAWJodHRwczovL20uYXotamVuYXRhLmJnL2EvNS16ZHJhdmUtaS1rcmFzb3RhLzYzNTM5LWthay1kYS1zdG9waXRlLWtvcmVtbml0ZS1tYXpuaW5pLXphLWxpYXRvdG8vYW1wLw?oc&lt;span class="o"&gt;=&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;Bulgarian&lt;span class="o"&gt;)&lt;/span&gt; - This article provides tips on losing belly fat &lt;span class="k"&gt;for &lt;/span&gt;the summer.

4. &lt;span class="o"&gt;[&lt;/span&gt;Recap: From RCC Diagnosis to Treatment, Toxicity Management, and Beyond]&lt;span class="o"&gt;(&lt;/span&gt;https://news.google.com/rss/articles/CBMiZ2h0dHBzOi8vd3d3LmNhbmNlcm5ldHdvcmsuY29tL3ZpZXcvcmVjYXAtZnJvbS1yY2MtZGlhZ25vc2lzLXRvLXRyZWF0bWVudC10b3hpY2l0eS1tYW5hZ2VtZW50LWFuZC1iZXlvbmTSAQA?oc&lt;span class="o"&gt;=&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt; - This article discusses the diagnosis, treatment, and management of renal cell carcinoma.

5. &lt;span class="o"&gt;[&lt;/span&gt;Review Highlights Progress, Challenges With CRS &lt;span class="k"&gt;in &lt;/span&gt;Cancer Immunotherapies]&lt;span class="o"&gt;(&lt;/span&gt;https://news.google.com/rss/articles/CBMiYmh0dHBzOi8vd3d3LmFqbWMuY29tL3ZpZXcvcmV2aWV3LWhpZ2hsaWdodHMtcHJvZ3Jlc3MtY2hhbGxlbmdlcy13aXRoLWNycy1pbi1jYW5jZXItaW1tdW5vdGhlcmFwaWVz0gEA?oc&lt;span class="o"&gt;=&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt; - This review highlights the progress and challenges &lt;span class="k"&gt;in &lt;/span&gt;cancer immunotherapies targeting CRS &lt;span class="o"&gt;(&lt;/span&gt;cytokine release syndrome&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

These are just a few of the recent developments &lt;span class="k"&gt;in &lt;/span&gt;the field of health. For more detailed information, you can &lt;span class="nb"&gt;read &lt;/span&gt;the full articles by clicking on the provided links.

&lt;span class="o"&gt;==&lt;/span&gt;End of &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can countries as well, enabling us to query the bot for news about a particular region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;What is the latest news from France?
Working...
Processing 34 articles from newsapi.org
Working...


&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nv"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;

Here are some of the latest news headlines &lt;span class="k"&gt;in &lt;/span&gt;France:

1. &lt;span class="s2"&gt;"Diplôme national du brevet session 2023"&lt;/span&gt; - The Ministry of National Education announces the national diploma &lt;span class="k"&gt;for &lt;/span&gt;the 2023 session. &lt;span class="o"&gt;[&lt;/span&gt;Read more]&lt;span class="o"&gt;(&lt;/span&gt;https://news.google.com/rss/articles/CBMiTGh0dHBzOi8vd3d3LmVkdWNhdGlvbi5nb3V2LmZyL2RpcGxvbWUtbmF0aW9uYWwtZHUtYnJldmV0LXNlc3Npb24tMjAyMy0zNzg1NjDSAQA?oc&lt;span class="o"&gt;=&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt;

2. &lt;span class="s2"&gt;"Cyclisme: la Nordiste Victoire Berteau sacrée championne de France après sa victoire sur les routes de Cassel"&lt;/span&gt; - Victoire Berteau from Nord wins the championship &lt;span class="k"&gt;in &lt;/span&gt;cycling &lt;span class="k"&gt;in &lt;/span&gt;France. &lt;span class="o"&gt;[&lt;/span&gt;Read more]&lt;span class="o"&gt;(&lt;/span&gt;https://news.google.com/rss/articles/CBMiiQFodHRwczovL3d3dy5mcmFuY2V0dmluZm8uZnIvc3BvcnRzL2N5Y2xpc21lL2N5Y2xpc21lLXZpY3RvaXJlLWJlcnRlYXUtc2FjcmVlLWNoYW1waW9ubmUtZGUtZnJhbmNlLWFwcmVzLXNhLXZpY3RvaXJlLWEtY2Fzc2VsXzU5MDg4NDcuaHRtbNIBAA?oc&lt;span class="o"&gt;=&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt;

3. &lt;span class="s2"&gt;"Guerre en Ukraine: comment les capitales étrangères réagissent-elles à la rébellion de la milice Wagner en Ru"&lt;/span&gt; - Foreign capitals&lt;span class="s1"&gt;' reactions to the rebellion of the Wagner militia in Ukraine. [Read more](https://news.google.com/rss/articles/CBMiwAFodHRwczovL3d3dy5mcmFuY2V0dmluZm8uZnIvbW9uZGUvZXVyb3BlL21hbmlmZXN0YXRpb25zLWVuLXVrcmFpbmUvZ3VlcnJlLWVuLXVrcmFpbmUtY29tbWVudC1sZXMtY2FwaXRhbGVzLWV0cmFuZ2VyZXMtcmVhZ2lzc2VudC1lbGxlcy1hLXJlYmVsbGlvbi1kZS1sYS1taWxpY2Utd2FnbmVyLWVuLXJ1c3NpZV81OTA4NzY2Lmh0bWzSAQA?oc=5)

4. "Marche des fiertés LGBT+: six jeunes mineurs interpellés pour homophobie" - Six minors arrested for homophobia during the LGBT+ Pride March. [Read more](https://news.google.com/rss/articles/CBMifmh0dHBzOi8vd3d3LnJ0bC5mci9hY3R1L2p1c3RpY2UtZmFpdHMtZGl2ZXJzL21hcmNoZS1kZXMtZmllcnRlcy1sZ2J0LXNpeC1qZXVuZXMtbWluZXVycy1pbnRlcnBlbGxlcy1wb3VyLWhvbW9waG9iaWUtNzkwMDI3Nzg4M9IBAA?oc=5)

5. "ATP 500 Queen'&lt;/span&gt;s - De Minaur a dominé Rune avec autorité: le film de la demi-finale&lt;span class="s2"&gt;" - Alex de Minaur dominates Rune in the ATP 500 Queen's semifinals. [Read more](https://news.google.com/rss/articles/CBMimwFodHRwczovL3d3dy5ldXJvc3BvcnQuZnIvdGVubmlzL2F0cC1sb25kcmVzLzIwMjMvYXRwLTUwMC1xdWVlbi1zLXN1aXZlei1sYS1kZW1pLWZpbmFsZS1lbnRyZS1hbGV4LWRlLW1pbmF1ci1ldC1ob2xnZXItcnVuZS1lbi1kaXJlY3Rfc3RvOTY3MTM4My9zdG9yeS5zaHRtbNIBAA?oc=5)

These are just a few of the latest news headlines in France. Let me know if you want more information about any specific news article.

==End of response==
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ideas for Improvement
&lt;/h3&gt;

&lt;p&gt;This simple bot is quite capable, even with a single function call. Now, imagine the possibilities if we integrate more features. Here are a few ideas to augment NewsGPT:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve the original articles to get summaries and analyze the news. We would need to navigate paywalls, perform web scraping, or check if RSS feeds or APIs provide content.&lt;/li&gt;
&lt;li&gt;Add more endpoints. NewsAPI offers endpoints for searching news by date, categories, and filtering through &lt;a href="https://newsapi.org/docs/endpoints/sources"&gt;sources&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Incorporate extra integrations, such as obtaining real-time data from sources like weather or finance.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Function calling is a powerful feature in OpenAI's GPT models, enabling them to interact with external tools and APIs in a more deterministic and structured manner. This feature lays the groundwork for more dynamic and responsive AI applications capable of providing current information and executing tasks beyond what was previously possible.&lt;/p&gt;

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

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Word Embeddings: Giving Your ChatBot Context For Better Answers</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Tue, 25 Jul 2023 22:19:52 +0000</pubDate>
      <link>https://forem.com/semaphore/word-embeddings-giving-your-chatbot-context-for-better-answers-156n</link>
      <guid>https://forem.com/semaphore/word-embeddings-giving-your-chatbot-context-for-better-answers-156n</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@iammottakin?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Mojahid Mottakin&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/ai?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is no doubt that OpenAI's ChatGPT is exceptionally intelligent — it has passed the &lt;a href="https://www.cnet.com/tech/chatgpt-can-pass-the-bar-exam-does-that-actually-matter/"&gt;lawyer's bar test&lt;/a&gt;, it possesses &lt;a href="https://aibusiness.com/verticals/chatgpt-passes-medical-board-exam"&gt;knowledge akin to a doctor&lt;/a&gt;, and some tests have &lt;a href="https://www.scientificamerican.com/article/i-gave-chatgpt-an-iq-test-heres-what-i-discovered/"&gt;clocked its IQ at 155&lt;/a&gt;. However, it tends to &lt;a href="https://news.ycombinator.com/item?id=33841672"&gt;fabricate information&lt;/a&gt; instead of conceding ignorance. This tendency, coupled with the fact that its knowledge ceases in 2021, poses challenges in building specialized products using the GPT API.&lt;/p&gt;

&lt;p&gt;How can we surmount these obstacles? How can we impart new knowledge to a model like GPT-3? My goal is to address these questions by constructing a question-answering bot employing Python, the OpenAI API, and word embeddings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Will Be Building
&lt;/h2&gt;

&lt;p&gt;I intend to create a bot that generates &lt;a href="https://semaphoreci.com/continuous-integration"&gt;continuous integration&lt;/a&gt; pipelines from a prompt, which, as you may know, &lt;a href="https://docs.semaphoreci.com/reference/pipeline-yaml-reference/"&gt;are formated with YAML&lt;/a&gt; in Semaphore CI/CD.&lt;/p&gt;

&lt;p&gt;Here is an example of the bot in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eGdUPIzD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/2c5670035a90da6ba1ad8ab9fb989857ba6614301dc8244e65302965df60e04c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eGdUPIzD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/2c5670035a90da6ba1ad8ab9fb989857ba6614301dc8244e65302965df60e04c.jpg" alt='Screenshot of the running program. On the screen, the command is executed: python query.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub", and the program prints out YAML corresponding to a CI pipeline that performs the requested action.' width="800" height="493"&gt;&lt;/a&gt;&lt;br&gt;
Screenshot of the running program. On the screen, the command &lt;code&gt;python query.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub"&lt;/code&gt; is executed, and the program prints out YAML corresponding to a CI pipeline that performs the requested action.&lt;/p&gt;

&lt;p&gt;In the spirit of projects like &lt;a href="https://docsgpt.arc53.com"&gt;DocsGPT&lt;/a&gt;, &lt;a href="https://myaskai.com/"&gt;My AskAI&lt;/a&gt;, and &lt;a href="https://libraria.dev/"&gt;Libraria&lt;/a&gt;, I plan to "teach" the GPT-3 model about Semaphore and how to generate pipeline configuration files. I will achieve this by leveraging the &lt;a href="https://docs.semaphoreci.com"&gt;existing documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will not assume prior knowledge of bot building and will maintain clean code so that you can adapt it to your requirements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You do not need experience in coding a bot or knowledge of neural networks to follow this tutorial. However, you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.&lt;/li&gt;
&lt;li&gt;A Pinecone account (sign up for the &lt;a href="https://www.pinecone.io/pricing/"&gt;Starter plan&lt;/a&gt; for free).&lt;/li&gt;
&lt;li&gt;An OpenAI API Key (&lt;a href="https://openai.com/pricing"&gt;paid, requires a credit card&lt;/a&gt;); new users can experiment with $5 in free credit during the first 3 months.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  But ChatGPT Can't Learn, Can It?
&lt;/h2&gt;

&lt;p&gt;ChatGPT, or more accurately, GPT-3 and GPT-4, the Large Language Models (LLMs) powering them, have been trained on a massive dataset with a cutoff date around September 2021.&lt;/p&gt;

&lt;p&gt;In essence, GPT-3 knows very little about events beyond that date. We can verify this with a simple prompt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ilYSCUnh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/fbd26c2e0c5e6eaefbce23bb12ff899808e7b10344c44e9c9c67c95eff97dc9a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ilYSCUnh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/fbd26c2e0c5e6eaefbce23bb12ff899808e7b10344c44e9c9c67c95eff97dc9a.jpg" alt="Screenshot of ChatGPT. The question is: 'Who won the World Cup 2022?' ChatGPT's answer is: 'As an AI language model, I don't have real-time information or the ability to browse the internet. As of my last knowledge update in September 2021, the FIFA World Cup 2022 has not occurred yet. The tournament is scheduled to be held in Qatar from November 21 to December 18, 2022. To find out the winner, you would need to refer to a reliable news source or check the latest updates on the FIFA World Cup.'" width="627" height="307"&gt;&lt;/a&gt;&lt;br&gt;
ChatGPT doesn't know who won the World Cup in 2022.&lt;/p&gt;

&lt;p&gt;While some OpenAI models can undergo &lt;a href="https://platform.openai.com/docs/guides/fine-tuning"&gt;fine-tuning&lt;/a&gt;, the more advanced models, such as the ones were interested in, cannot; we cannot augment their training data.&lt;/p&gt;

&lt;p&gt;How can we get answers from GPT-3 beyond its training data? One method involves exploiting its text comprehension abilities; by enhancing the prompt with relevant context, we can likely obtain the correct answer.&lt;/p&gt;

&lt;p&gt;In the example below, I provide context from &lt;a href="https://www.fifa.com/fifaplus/en/tournaments/mens/worldcup/qatar2022"&gt;FIFA's official site&lt;/a&gt;, and the response differs significantly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pBB6pU3P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/d620932dfc29ceea5499f83e37070034fdf47bc5a3984597709762bb1aa3d66b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pBB6pU3P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/d620932dfc29ceea5499f83e37070034fdf47bc5a3984597709762bb1aa3d66b.jpg" alt="Second attempt at responding to the question. Screenshot of ChatGPT. The question is: 'Reply to the last question with the following context extracted from FIFA's official website: THE FINAL It was the perfect final to an unforgettable FIFA World Cup™, with Argentina and France delivering an all-time classic at Lusail Stadium. La Albiceleste looked to be in control for most of the match and were two goals to the good by half-time, thanks to Lionel Messi and Angel Di Maria. However, France produced a stirring late comeback inspired by two goals from star man Kylian Mbappé. During a breathless and breathtaking extra time, Argentina regained the lead with Messi's second goal, only for Mbappe to level again. Ultimately, the match was decided by penalties, with Gonzalo Montiel clinching Argentina's historic triumph.' ChatGPT correctly answers: 'According to the context you provided, Argentina won the FIFA World Cup 2022 after defeating France in the final at Lusail Stadium. Argentina initially led the match with goals from Lionel Messi and Angel Di Maria, but France made a late comeback with two goals from Kylian Mbappé. The match proceeded to extra time, during which Argentina regained the lead through Messi's second goal. However, Mbappé equalized once more. The match was ultimately decided by a penalty shootout, with Gonzalo Montiel securing Argentina's victory. It's important to note that this information is purely fictional, as my knowledge was last updated in September 2021, and I cannot provide real-time updates or access current events.'" width="620" height="665"&gt;&lt;/a&gt;&lt;br&gt;
With the supplied context, ChatGPT can answer accurately.&lt;/p&gt;

&lt;p&gt;We can deduce that the model can respond to any prompt if given enough relevant context. The question remains: how can we know what's relevant given an arbitrary prompt? To address this, we need to explore what &lt;strong&gt;word embeddings&lt;/strong&gt; are.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Are Word Embeddings?
&lt;/h2&gt;

&lt;p&gt;In the context of language models, an embedding is a way of representing words, sentences, or entire documents as vectors or lists of numbers.&lt;/p&gt;

&lt;p&gt;To calculate embeddings, we will need a neural network such as &lt;a href="https://www.tensorflow.org/tutorials/text/word2vec"&gt;word2vec&lt;/a&gt; or &lt;a href="https://platform.openai.com/docs/guides/embeddings/what-are-embeddings"&gt;text-embedding-ada-002&lt;/a&gt;. These networks have been trained on massive amounts of text and can find relationships between words by analyzing the frequencies with which specific patterns appear in the training data.&lt;/p&gt;

&lt;p&gt;Let’s say we have the following words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cat&lt;/li&gt;
&lt;li&gt;Dog&lt;/li&gt;
&lt;li&gt;Ball&lt;/li&gt;
&lt;li&gt;House&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine we use one of these embedding networks to calculate the vectors for each word. For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Word&lt;/th&gt;
&lt;th&gt;Vector&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cat&lt;/td&gt;
&lt;td&gt;[0.1, 0.2, 0.3, 0.4, 0.5]&lt;/td&gt;
&lt;td&gt;Animals, objects, small things&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dog&lt;/td&gt;
&lt;td&gt;[0.6, 0.7, 0.8, 0.9, 1.0]&lt;/td&gt;
&lt;td&gt;Animals, objects, large things&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ball&lt;/td&gt;
&lt;td&gt;[0.2, 0.4, 0.6, 0.8, 1.0]&lt;/td&gt;
&lt;td&gt;Objects, toys, small things&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;House&lt;/td&gt;
&lt;td&gt;[0.3, 0.6, 0.9, 1.2, 1.5]&lt;/td&gt;
&lt;td&gt;Buildings, homes, large things&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once we have the vectors for each word, we can use them to represent the meaning of the text. For example, the sentence “The cat chased the ball” can be represented as the vector [0.1, 0.2, 0.3, 0.4, 0.5] + [0.2, 0.4, 0.6, 0.8, 1.0] = [0.3, 0.6, 0.9, 1.2, 1.5]. This vector represents a sentence that is about an animal chasing an object.&lt;/p&gt;

&lt;p&gt;Word embeddings can be visualized as multidimensional spaces where words or sentences with similar meanings are close together. We can compute the "distance" between vectors to find similar meanings for any input text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E9jcwhHS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/a47e98dc9fe5e345ab50b39ac0c5c56fc429d3db92d4aca16db94de1d0eefbae.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E9jcwhHS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/a47e98dc9fe5e345ab50b39ac0c5c56fc429d3db92d4aca16db94de1d0eefbae.jpg" alt="Three tridimensional representations of vectors. The first one is labeled 'Male-Female' and have data points man-woman and king-queen, the second is labeled 'Verb-Tense' and has verbs like walking-walked swimming-swam. The last is labeled 'Country-Capital' and has several capitals connected to their countries" width="800" height="305"&gt;&lt;/a&gt;&lt;br&gt;
3D representation of embeddings as vector spaces. In reality, these spaces can have hundreds or thousands of dimensions. Source: &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/meet-ais-multitool-vector-embeddings"&gt;Meet AI’s Multitool: Vector Embeddings&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The actual mathematics behind all this is beyond the scope of this article. However, the key takeaway is that &lt;strong&gt;vector operations allow us to manipulate or determine meaning using mathematics&lt;/strong&gt;. Take the vector that represents the word “queen,” subtract the “woman” vector from it, and add the “man” vector. The result should be a vector in the vicinity of “king.” If we add “son,” we should get somewhere close to “prince.”&lt;/p&gt;
&lt;h3&gt;
  
  
  Embedding Neural Networks with Tokens
&lt;/h3&gt;

&lt;p&gt;So far, we have discussed embedding neural networks taking words as inputs and numbers as outputs. However, many modern networks have moved from processing words to processing tokens.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;token&lt;/strong&gt; is the smallest unit of text that can be processed by the model. Tokens can be words, characters, punctuation marks, symbols, or parts of words.&lt;/p&gt;

&lt;p&gt;We can see how words are converted to tokens by experimenting with the &lt;a href="https://platform.openai.com/tokenizer"&gt;OpenAI online tokenizer&lt;/a&gt;, which uses &lt;a href="https://en.wikipedia.org/wiki/Byte_pair_encoding"&gt;Byte-Pair Encoding&lt;/a&gt; (BPE) to convert text to tokens and represent each one with a number:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vbykLyH5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/445c1200f80302b3609f11b46571281e0ff39f34f3d1c4a01690fb6eb6e4c1ba.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vbykLyH5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/445c1200f80302b3609f11b46571281e0ff39f34f3d1c4a01690fb6eb6e4c1ba.jpg" alt="Screenshot of OpenAI tokenizer. Some text has been input, and each token is represented by different colors, allowing us to see how words are mapped to tokens. The text reads: Behind any embedding model, there is a neural network that converts the input text into vectors. Each type of embedding model has different capabilities and speeds. Word2vec, for instance, takes words and produces vectors in the range of 100 to 300 dimensions." width="708" height="283"&gt;&lt;/a&gt;&lt;br&gt;
There is often a 1-to-1 relationship between tokens and words. Most tokens include the word and a leading space. However, there are special cases like "embedding," which consists of two tokens, "embed" and "ding," or "capabilities," which consists of four tokens. If you click "Token IDs," you can see the model's numeric representation of each token.&lt;/p&gt;
&lt;h2&gt;
  
  
  Designing a Smarter Bot Using Embeddings
&lt;/h2&gt;

&lt;p&gt;Now that we have an understanding of what embeddings are, the next question is: &lt;strong&gt;how can they help us build a smarter bot?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, let's consider what happens when we use the GPT-3 API directly. The user issues a prompt, and the model responds to the best of its ability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OQPkXdbp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/2234a6c50522cdd689eb40c82fd5baa67952d0b85a9f7543735d9dd0f1f4a2e6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OQPkXdbp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/2234a6c50522cdd689eb40c82fd5baa67952d0b85a9f7543735d9dd0f1f4a2e6.jpg" alt="Diagram showing interaction between user and GPT-3. User sends a prompt, the model responds." width="568" height="236"&gt;&lt;/a&gt; However, when we add context to the equation, things change. For example, when I asked ChatGPT about the winner of the World Cup after providing context, it made all the difference.&lt;/p&gt;

&lt;p&gt;So, the plan to build a smarter bot is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intercept the user's prompt.&lt;/li&gt;
&lt;li&gt;Calculate the embeddings for that prompt, yielding a vector.&lt;/li&gt;
&lt;li&gt;Search a database for documents near the vector, as they should be semantically relevant to the initial prompt.&lt;/li&gt;
&lt;li&gt;Send the original prompt to GPT-3, along with any relevant context.&lt;/li&gt;
&lt;li&gt;Forward GPT-3's response to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--efy55dPD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/cdf18fb74391632d413028b51c58f10c1bebdeda0ea2fe6612a638755ce74d32.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--efy55dPD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/cdf18fb74391632d413028b51c58f10c1bebdeda0ea2fe6612a638755ce74d32.jpg" alt="A more complex implementation of a bot. The user sends the prompt to a chatbot app, which searches a context database and uses that to enrich the prompt. The prompt is sent to GPT-3, and its response is forwarded to the user." width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's begin like most projects, by designing the database.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a Knowledge Database with Embeddings
&lt;/h2&gt;

&lt;p&gt;Our context database must include the original documentation and their respective vectors. In principle, we can employ any type of database for this task, but a &lt;strong&gt;vector database&lt;/strong&gt; is the optimal tool for the job.&lt;/p&gt;

&lt;p&gt;Vector databases are specialized databases designed to store and retrieve high-dimensional vector data. Instead of employing a query language such as SQL for searching, we supply a vector and request the N closest neighbors.&lt;/p&gt;

&lt;p&gt;To generate the vectors, we will use &lt;a href="https://openai.com/blog/new-and-improved-embedding-model"&gt;text-embedding-ada-002&lt;/a&gt; from OpenAI, as it is the fastest and most cost-effective model they offer. The model converts the input text into tokens and uses an attention mechanism known as &lt;a href="https://arxiv.org/abs/1706.03762"&gt;Transformer&lt;/a&gt; to learn their relationships. The output of this neural network is vectors representing the meaning of the text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h_NKRwgV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/c10b15aec9a65372833a8bad619dd9a71a43c9e135a244f009a66bbc5a49ec84.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h_NKRwgV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/c10b15aec9a65372833a8bad619dd9a71a43c9e135a244f009a66bbc5a49ec84.jpg" alt="Diagram illustrating the tokenization process. A document is tokenized and then sent to an embedding neural network. The output of the network is a vector." width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To create a context database, I will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collect all the source documentation.&lt;/li&gt;
&lt;li&gt;Filter out irrelevant documents.&lt;/li&gt;
&lt;li&gt;Calculate the embeddings for each document.&lt;/li&gt;
&lt;li&gt;Store the vectors, original text, and any other relevant metadata in the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fnIWhyTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/6e6a6ad40e4b959867b2a3c28ff7f5b3bebc61a006b7cf03efd78a577690e934.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fnIWhyTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/6e6a6ad40e4b959867b2a3c28ff7f5b3bebc61a006b7cf03efd78a577690e934.jpg" alt="Diagram illustrating the process of storing data in the context database. The source document is sent to the embedding neural network. The database stores the vector along with the original text." width="781" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Converting Documents into Vectors
&lt;/h3&gt;

&lt;p&gt;First, I must initialize an environment file with the OpenAI API key. This file should never be committed to version control, as the API key is private and tied to your account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I'll create a virtualenv for my Python application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;virtualenv venv
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And install the OpenAI package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
$ pip install openai numpy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Let's try calculating the embedding for the string "Docker Container". You can run this on the Python REPL or as a Python script:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 python&lt;br&gt;
$ python&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;import openai&lt;/p&gt;

&lt;p&gt;embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")&lt;/p&gt;

&lt;p&gt;embeddings&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;JSON: {&lt;br&gt;
 "data": [&lt;br&gt;
 {&lt;br&gt;
 "embedding": [&lt;br&gt;
 -0.00530336843803525,&lt;br&gt;
 0.0013223182177171111,&lt;/p&gt;

&lt;p&gt;... 1533 more items ...,&lt;/p&gt;

&lt;p&gt;-0.015645816922187805&lt;br&gt;
 ],&lt;br&gt;
 "index": 0,&lt;br&gt;
 "object": "embedding"&lt;br&gt;
 }&lt;br&gt;
 ],&lt;br&gt;
 "model": "text-embedding-ada-002-v2",&lt;br&gt;
 "object": "list",&lt;br&gt;
 "usage": {&lt;br&gt;
 "prompt_tokens": 2,&lt;br&gt;
 "total_tokens": 2&lt;br&gt;
 }&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
As you can see, OpenAI's model responds with an `embedding` list containing 1536 items — the vector size for text-embedding-ada-002.

## Storing the embeddings in Pinecone

While there are multiple vector database engines to choose from, like [Chroma](https://www.trychroma.com/) which is open-source, I chose [Pinecone](https://www.pinecone.io/) because its a managed database with a free tier, which makes things simpler. Their [Starter plan](https://www.pinecone.io/pricing/) is more than capable of handling all the data I will need.

After creating my Pinecone account and retrieving my API key and environment, I add both values to my `.env` file.


![Pinecone API Key generation screenshot](https://raw.githubusercontent.com/tomfern/blog-assets/master/images/aa79dbcadd804a2b1aefe607553f50e960619717590f573da6ca04e967668802.jpg)

Now `.env` should contain my Pinecone and OpenAI secrets.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 bash&lt;br&gt;
export OPENAI_API_KEY=YOUR_API_KEY&lt;/p&gt;
&lt;h1&gt;
  
  
  Pinecone secrets
&lt;/h1&gt;

&lt;p&gt;export PINECONE_API_KEY=YOUR_API_KEY&lt;br&gt;
export PINECONE_ENVIRONMENT=YOUR_PINECONE_DATACENTER&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Then, I install the Pinecone client for Python:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 bash&lt;br&gt;
$ pip install pinecone-client&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
I need to initialize a database; these are the contents of the `db_create.py` script:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 python&lt;/p&gt;
&lt;h1&gt;
  
  
  db_create.py
&lt;/h1&gt;

&lt;p&gt;import pinecone&lt;br&gt;
import openai&lt;br&gt;
import os&lt;/p&gt;

&lt;p&gt;index_name = "semaphore"&lt;br&gt;
embed_model = "text-embedding-ada-002"&lt;/p&gt;

&lt;p&gt;api_key = os.getenv("PINECONE_API_KEY")&lt;br&gt;
env = os.getenv("PINECONE_ENVIRONMENT")&lt;br&gt;
pinecone.init(api_key=api_key, environment=env)&lt;/p&gt;

&lt;p&gt;embedding = openai.Embedding.create(&lt;br&gt;
 input=[&lt;br&gt;
 "Sample document text goes here",&lt;br&gt;
 "there will be several phrases in each batch"&lt;br&gt;
 ], engine=embed_model&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;if index_name not in pinecone.list_indexes():&lt;br&gt;
 print("Creating pinecone index: " + index_name)&lt;br&gt;
 pinecone.create_index(&lt;br&gt;
 index_name,&lt;br&gt;
 dimension=len(embedding['data'][0]['embedding']),&lt;br&gt;
 metric='cosine',&lt;br&gt;
 metadata_config={'indexed': ['source', 'id']}&lt;br&gt;
 )&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
The script can take a few minutes to create the database.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 bash&lt;br&gt;
$ python db_create.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Next, I will install the [tiktoken](https://github.com/openai/tiktoken) package. I'll use it to calculate how many tokens the source documents have. This is important because the embedding model can only handle up to 8191 tokens.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 bash&lt;br&gt;
$ pip install tiktoken&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
While installing packages, let's also install `tqdm` to produce a nice-looking progress bar.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 bash&lt;br&gt;
$ pip install tqdm&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Now I need to upload the documents to the database. The script for this will be called `index_docs.py`. Let's start by importing the required modules and defining some constants:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 python&lt;/p&gt;
&lt;h1&gt;
  
  
  index_docs.py
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Pinecone db name and upload batch size
&lt;/h1&gt;

&lt;p&gt;index_name = 'semaphore'&lt;br&gt;
upsert_batch_size = 20&lt;/p&gt;
&lt;h1&gt;
  
  
  OpenAI embedding and tokenizer models
&lt;/h1&gt;

&lt;p&gt;embed_model = "text-embedding-ada-002"&lt;br&gt;
encoding_model = "cl100k_base"&lt;br&gt;
max_tokens_model = 8191&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Next, we'll need a function to count tokens. There is a [token counter example](https://platform.openai.com/docs/guides/embeddings/how-can-i-tell-how-many-tokens-a-string-has-before-i-embed-it) on OpenAI page:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 python&lt;br&gt;
import tiktoken&lt;br&gt;
def num_tokens_from_string(string: str) -&amp;gt; int:&lt;br&gt;
 """Returns the number of tokens in a text string."""&lt;br&gt;
 encoding = tiktoken.get_encoding(encoding_model)&lt;br&gt;
 num_tokens = len(encoding.encode(string))&lt;br&gt;
 return num_tokens&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Finally, I'll need some filtering functions to convert the original document into usable examples. Most examples in the documentation are between code fences, so I'll just extract all YAML code from every file:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 python&lt;br&gt;
import re&lt;br&gt;
def extract_yaml(text: str) -&amp;gt; str:&lt;br&gt;
 """Returns list with all the YAML code blocks found in text."""&lt;br&gt;
 matches = [m.group(1) for m in re.finditer("&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
", text)]
 return matches


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

&lt;/div&gt;

&lt;p&gt;I'm done with the functions. Next, this will load the files in memory and extract the examples:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python

from tqdm import tqdm
import sys
import os
import pathlib

repo_path = sys.argv[1]
repo_path = os.path.abspath(repo_path)
repo = pathlib.Path(repo_path)

markdown_files = list(repo.glob("**/*.md")) + list(
 repo.glob("**/*.mdx")
)

print(f"Extracting YAML from Markdown files in {repo_path}")
new_data = []
for i in tqdm(range(0, len(markdown_files))):
 markdown_file = markdown_files[i]
 with open(markdown_file, "r") as f:
 relative_path = markdown_file.relative_to(repo_path)
 text = str(f.read())
 if text == '':
 continue
 yamls = extract_yaml(text)
 j = 0
 for y in yamls:
 j = j+1
 new_data.append({
 "source": str(relative_path),
 "text": y,
 "id": f"github.com/semaphore/docs/{relative_path}[{j}]"
 })


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

&lt;/div&gt;

&lt;p&gt;At this point, all the YAMLs should be stored in the &lt;code&gt;new_data&lt;/code&gt; list. The final step is to upload the embeddings into Pinecone.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python

import pinecone
import openai

api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, enviroment=env)
index = pinecone.Index(index_name)

print(f"Creating embeddings and uploading vectors to database")
for i in tqdm(range(0, len(new_data), upsert_batch_size)):

 i_end = min(len(new_data), i+upsert_batch_size)
 meta_batch = new_data[i:i_end]
 ids_batch = [x['id'] for x in meta_batch]
 texts = [x['text'] for x in meta_batch]

 embedding = openai.Embedding.create(input=texts, engine=embed_model)
 embeds = [record['embedding'] for record in embedding['data']]

 # clean metadata before upserting
 meta_batch = [{
 'id': x['id'],
 'text': x['text'],
 'source': x['source']
 } for x in meta_batch] 

 to_upsert = list(zip(ids_batch, embeds, meta_batch))
 index.upsert(vectors=to_upsert)


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

&lt;/div&gt;

&lt;p&gt;As a reference, you can find the full &lt;a href="https://github.com/TomFern/semaphore-demo-pipelinegpt/blob/master/database/index_docs.py"&gt;index_docs.py&lt;/a&gt; file in the demo repository&lt;/p&gt;

&lt;p&gt;Let's run the index script to finish with the database setup:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 bash
$ git clone https://github.com/semaphoreci/docs.git /tmp/docs
$ source .env
$ python index_docs.py /tmp/docs


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Testing the database
&lt;/h3&gt;

&lt;p&gt;The Pinecone dashboard should show vectors in the database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hj7xBu2N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/a1ac70a539a89621e4370a1f8abc6833cd7449e3a636f008b3be17b9e00dc456.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hj7xBu2N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/a1ac70a539a89621e4370a1f8abc6833cd7449e3a636f008b3be17b9e00dc456.jpg" alt="Screenshot of Pinecone dashboard showing the database with a total of 79 vectors" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can query the database with the following code, which you can run as a script or in the Python REPL directly:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
$ python

&amp;gt;&amp;gt;&amp;gt; import os
&amp;gt;&amp;gt;&amp;gt; import pinecone
&amp;gt;&amp;gt;&amp;gt; import openai

# Compute embeddings for string "Docker Container"
&amp;gt;&amp;gt;&amp;gt; embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")


# Connect to database
&amp;gt;&amp;gt;&amp;gt; index_name = "semaphore"
&amp;gt;&amp;gt;&amp;gt; api_key = os.getenv("PINECONE_API_KEY")
&amp;gt;&amp;gt;&amp;gt; env = os.getenv("PINECONE_ENVIRONMENT")
&amp;gt;&amp;gt;&amp;gt; pinecone.init(api_key=api_key, environment=env)
&amp;gt;&amp;gt;&amp;gt; index = pinecone.Index(index_name)

# Query database
&amp;gt;&amp;gt;&amp;gt; matches = index.query(embeddings['data'][0]['embedding'], top_k=1, include_metadata=True)

&amp;gt;&amp;gt;&amp;gt; matches['matches'][0]
{'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
 'metadata': {'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
 'source': 'docs/ci-cd-environment/docker-authentication.md',
 'text': '\n'
 '# .semaphore/semaphore.yml\n'
 'version: v1.0\n'
 'name: Using a Docker image\n'
 'agent:\n'
 ' machine:\n'
 ' type: e1-standard-2\n'
 ' os_image: ubuntu1804\n'
 '\n'
 'blocks:\n'
 ' - name: Run container from Docker Hub\n'
 ' task:\n'
 ' jobs:\n'
 ' - name: Authenticate docker pull\n'
 ' commands:\n'
 ' - checkout\n'
 ' - echo $DOCKERHUB_PASSWORD | docker login '
 '--username "$DOCKERHUB_USERNAME" --password-stdin\n'
 ' - docker pull /\n'
 ' - docker images\n'
 ' - docker run /\n'
 ' secrets:\n'
 ' - name: docker-hub\n'},
 'score': 0.796259582,
 'values': []}


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

&lt;/div&gt;

&lt;p&gt;As you can see, the first match is the YAML for a Semaphore pipeline that pulls a Docker image and runs it. It's a good start since it's relevant to our "Docker Containers" search string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the bot
&lt;/h2&gt;

&lt;p&gt;We have the data, and we know how to query it. Let's put it to work in the bot.&lt;/p&gt;

&lt;p&gt;The steps for processing the prompt is: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the user's prompt. &lt;/li&gt;
&lt;li&gt;Calculate its vector. &lt;/li&gt;
&lt;li&gt;Retrieve relevant context from the database. &lt;/li&gt;
&lt;li&gt;Send the user's prompt along with context to GPT-3. &lt;/li&gt;
&lt;li&gt;Forward the model's response to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fn4S-K1l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/b843a57a97ccaed9476725b74680fa0b53dff9e37f6ba31505bd3e4a57d9a621.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fn4S-K1l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/b843a57a97ccaed9476725b74680fa0b53dff9e37f6ba31505bd3e4a57d9a621.jpg" alt="Diagram of the data flow for the bot. On the left the user prompt enters, which is processed by the embedding neural network, then sent to the context database. The search yields relevant text which is sent to the GPT-3 model. The output of the model is sent to the user as the final answer." width="800" height="172"&gt;&lt;/a&gt; As usual, I'll start by defining some constants in &lt;code&gt;complete.py&lt;/code&gt;, the bot's main script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
# complete.py

# Pinecone database name, number of matched to retrieve
# cutoff similarity score, and how much tokens as context
index_name = 'semaphore'
context_cap_per_query = 30
match_min_score = 0.75
context_tokens_per_query = 3000

# OpenAI LLM model parameters
chat_engine_model = "gpt-3.5-turbo"
max_tokens_model = 4096
temperature = 0.2 
embed_model = "text-embedding-ada-002"
encoding_model_messages = "gpt-3.5-turbo-0301"
encoding_model_strings = "cl100k_base"

import pinecone
import os

# Connect with Pinecone db and index
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, environment=env)
index = pinecone.Index(index_name)


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

&lt;/div&gt;

&lt;p&gt;Next, I'll add functions to count tokens as shown in the &lt;a href="https://platform.openai.com/docs/guides/chat/managing-tokens"&gt;OpenAI examples&lt;/a&gt;. The first function counts tokens in a string, while the second counts tokens in messages. We'll see messages in detail in a bit. For now, let's just say it's a structure that keeps the state of the conversation in memory.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
import tiktoken

def num_tokens_from_string(string: str) -&amp;gt; int:
 """Returns the number of tokens in a text string."""
 encoding = tiktoken.get_encoding(encoding_model_strings)
 num_tokens = len(encoding.encode(string))
 return num_tokens


def num_tokens_from_messages(messages):
 """Returns the number of tokens used by a list of messages. Compatible with model """

 try:
 encoding = tiktoken.encoding_for_model(encoding_model_messages)
 except KeyError:
 encoding = tiktoken.get_encoding(encoding_model_strings)

 num_tokens = 0
 for message in messages:
 num_tokens += 4 # every message follows {role/name}\n{content}\n
 for key, value in message.items():
 num_tokens += len(encoding.encode(value))
 if key == "name": # if there's a name, the role is omitted
 num_tokens += -1 # role is always required and always 1 token
 num_tokens += 2 # every reply is primed with assistant
 return num_tokens


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

&lt;/div&gt;

&lt;p&gt;The following function takes the original prompt and context strings to return an enriched prompt for GPT-3:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
def get_prompt(query: str, context: str) -&amp;gt; str:
 """Return the prompt with query and context."""
 return (
 f"Create the continuous integration pipeline YAML code to fullfil the requested task.\n" +
 f"Below you will find some context that may help. Ignore it if it seems irrelevant.\n\n" +
 f"Context:\n{context}" +
 f"\n\nTask: {query}\n\nYAML Code:"
 )


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;get_message&lt;/code&gt; function formats the prompt in a format compatible with API:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
def get_message(role: str, content: str) -&amp;gt; dict:
 """Generate a message for OpenAI API completion."""
 return {"role": role, "content": content}


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

&lt;/div&gt;

&lt;p&gt;There are three types of roles that affect how the model reacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User&lt;/strong&gt;: for the user's original prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System&lt;/strong&gt;: helps set the behavior of the assistant. While there is &lt;a href="https://community.openai.com/t/the-system-role-how-it-influences-the-chat-behavior/87353"&gt;some controversy&lt;/a&gt; regarding its effectiveness, it appears to be more effective when sent at the end of the messages list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assistant&lt;/strong&gt;: represents past responses of the model. The OpenAI API does not have a "memory"; instead, we must send the model's previous responses back during each interaction to maintain the conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now for the engaging part. The &lt;code&gt;get_context&lt;/code&gt; function takes the prompt, queries the database, and generates a context string until one of these conditions is met:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The complete text exceeds &lt;code&gt;context_tokens_per_query&lt;/code&gt;, the space I reserved for context.&lt;/li&gt;
&lt;li&gt;The search function retrieves all requested matches.&lt;/li&gt;
&lt;li&gt;Matches that have a similarity score below &lt;code&gt;match_min_score&lt;/code&gt; are ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
import openai

def get_context(query: str, max_tokens: int) -&amp;gt; list:
 """Generate message for OpenAI model. Add context until hitting `context_token_limit` limit. Returns prompt string."""

 embeddings = openai.Embedding.create(
 input=[query],
 engine=embed_model
 )

 # search the database
 vectors = embeddings['data'][0]['embedding']
 embeddings = index.query(vectors, top_k=context_cap_per_query, include_metadata=True)
 matches = embeddings['matches']

 # filter and aggregate context
 usable_context = ""
 context_count = 0
 for i in range(0, len(matches)):

 source = matches[i]['metadata']['source']
 if matches[i]['score'] &amp;lt; match_min_score:
 # skip context with low similarity score
 continue

 context = matches[i]['metadata']['text']
 token_count = num_tokens_from_string(usable_context + '\n---\n' + context)

 if token_count &amp;lt; context_tokens_per_query:
 usable_context = usable_context + '\n---\n' + context 
 context_count = context_count + 1

 print(f"Found {context_count} contexts for your query")

 return usable_context


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

&lt;/div&gt;

&lt;p&gt;The next and final function, &lt;code&gt;complete&lt;/code&gt;, issues the API request to OpenAI and returns the model's response.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
def complete(messages):
 """Query the OpenAI model. Returns the first answer. """

 res = openai.ChatCompletion.create(
 model=chat_engine_model,
 messages=messages,
 temperature=temperature
 )
 return res.choices[0].message.content.strip()


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

&lt;/div&gt;

&lt;p&gt;That's all; now I only have to deal with the command line arguments and call the functions in the correct order:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python
import sys

query = sys.argv[1]

context = get_context(query, context_tokens_per_query)
prompt = get_prompt(query, context)

# initialize messages list to send to OpenAI API
messages = []
messages.append(get_message('user', prompt))
messages.append(get_message('system', 'You are a helpful assistant that writes YAML code for Semaphore continuous integration pipelines and explains them. Return YAML code inside code fences.'))

if num_tokens_from_messages(messages) &amp;gt;= max_tokens_model:
 raise Exception('Model token size limit reached') 

print("Working on your query... ")
answer = complete(messages)
print("Answer:\n")
print(answer)
messages.append(get_message('assistant', answer))


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

&lt;/div&gt;

&lt;p&gt;It's time to run the script and see how it fares:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 bash
$ python complete.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub"


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

&lt;/div&gt;

&lt;p&gt;The result is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 yaml
version: v1.0
name: Docker Build and Push
agent:
 machine:
 type: e1-standard-2
 os_image: ubuntu1804

blocks:
 - name: "Build and Push Docker Image"
 task:
 jobs:
 - name: "Docker Build and Push"
 commands:
 - checkout
 - docker build -t /: .
 - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
 - docker push /:

promotions:
 - name: Deploy to production
 pipeline_file: deploy-production.yml
 auto_promote:
 when: "result = 'passed' and branch = 'master'"


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

&lt;/div&gt;

&lt;p&gt;This is the first good result. The model has inferred the syntax from the context examples we provided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thoughts on Expanding the Bot's Capabilities
&lt;/h2&gt;

&lt;p&gt;Remember that I started with a modest goal: creating an assistant to write YAML pipelines. With richer content in my vector database, I can generalize the bot to answer any question about Semaphore (or any product — remember cloning the docs into &lt;code&gt;/tmp&lt;/code&gt;?).&lt;/p&gt;

&lt;p&gt;The key to obtaining good answers is — unsurprisingly — quality context. Merely uploading every document into the vector database is unlikely to yield good results. The context database should be curated, tagged with descriptive metadata, and be concise. Otherwise, we risk filling the token quota in the prompt with irrelevant context.&lt;/p&gt;

&lt;p&gt;So, in a sense, there is an art — and a great deal of trial and error — involved in fine-tuning the bot to meet our needs. We can experiment with the context limit, remove low-quality content, summarize, and filter out irrelevant context by adjusting the similarity score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing a Proper Chatbot
&lt;/h3&gt;

&lt;p&gt;You may have noticed that my bot does not enable us to have actual conversation like ChatGPT. We ask one question and get one answer.&lt;/p&gt;

&lt;p&gt;Converting the bot into a fully-fledged chatbot is, in principle, not too challenging. We can maintain the conversation by resending previous responses to the model with each API request. Prior GPT-3 answers are sent back under the "assistant" role. For example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 python

messages = []

while True:

 query = input('Type your prompt:\n')

 context = get_context(query, context_tokens_per_query)
 prompt = get_prompt(query, context)
 messages.append(get_message('user', prompt))
 messages.append(get_message('system', 'You are a helpful assistant that writes YAML code for Semaphore continuous integration pipelines and explains them. Return YAML code inside code fences.'))

 if num_tokens_from_messages(messages) &amp;gt;= max_tokens_model:
 raise Exception('Model token size limit reached') 

 print("Working on your query... ")
 answer = complete(messages)
 print("Answer:\n")
 print(answer)

 # remove system message and append model's answer
 messages.pop() 
 messages.append(get_message('assistant', answer))


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

&lt;/div&gt;



&lt;p&gt;Unfortunately, this implementation is rather rudimentary. It will not support extended conversations as the token count increases with each interaction. Soon enough, we will reach the 4096-token limit for GPT-3, preventing further dialogue.&lt;/p&gt;

&lt;p&gt;So, we have to find some way of keeping the request within token limits. A few strategies follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete older messages. While this is the simplest solution, it limits the conversation's "memory" to only the most recent messages.&lt;/li&gt;
&lt;li&gt;Summarize previous messages. We can utilize "Ask the model" to condense earlier messages and substitute them for the original questions and answers. Though this approach increases the cost and lag between queries, it may produce superior outcomes compared to simply deleting past messages.&lt;/li&gt;
&lt;li&gt;Set a strict limit on the number of interactions.&lt;/li&gt;
&lt;li&gt;Wait for the GPT-4 API general availability, which is not only smarter but has double token capacity.&lt;/li&gt;
&lt;li&gt;Use a newer model like "gpt-3.5-turbo-16k" which &lt;a href="https://openai.com/blog/function-calling-and-other-api-updates"&gt;can handle up to 16k tokens&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Enhancing the bot's responses is possible with word embeddings and a good context databse. To achieve this, we need good quality documentation. There is a substantial amount of trial and error involved in developing a bot that seemingly possesses a grasp of the subject matter.&lt;/p&gt;

&lt;p&gt;I hope this in-depth exploration of word embeddings and large language models aids you in building a more potent bot, customized to your requirements.&lt;/p&gt;

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

</description>
      <category>ai</category>
      <category>gpt3</category>
      <category>tutorial</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Breaking Free From the Cloud With MRSK: Just Enough Orchestration for Your Apps</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Wed, 19 Jul 2023 21:07:32 +0000</pubDate>
      <link>https://forem.com/semaphore/breaking-free-from-the-cloud-with-mrsk-just-enough-orchestration-for-your-apps-48fe</link>
      <guid>https://forem.com/semaphore/breaking-free-from-the-cloud-with-mrsk-just-enough-orchestration-for-your-apps-48fe</guid>
      <description>&lt;p&gt;&lt;em&gt;A lightweight alternative to Kubernetes. Powering the likes of Hey.com and Basecamp.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Breaking Free From the Cloud With Mrsk: Just Enough Orchestration for Your Apps
&lt;/h1&gt;

&lt;p&gt;There is no doubt that the cloud has changed the way we run our software. Startups, for instance, can get started without buying expensive hardware and scale flexibly. Also, the cloud has enabled novel solutions such as serverless, managed Kubernetes and Docker, or edge functions. For a time, cloud-native applications seemed to be the way forward for most teams, big or small.&lt;/p&gt;

&lt;p&gt;But in exchange for all this power, we pay a cost. And it can be a steep one. &lt;a href="https://37signals.com/"&gt;37signals&lt;/a&gt; — the company behind HEY.com and Basecamp.com — has calculated that by buying a few servers and moving from the cloud to on-premise, they can save &lt;a href="https://world.hey.com/dhh/we-stand-to-save-7m-over-five-years-from-our-cloud-exit-53996caa"&gt;7 million dollars over 5 years&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And it is not an isolated case. Recently, Amazon Prime — the poster child of serverless architectures — moved part of its infrastructure from &lt;a href="https://thestack.technology/amazon-prime-video-microservices-monolith/"&gt;serverless into a monolith&lt;/a&gt; and cut costs by 90%.&lt;/p&gt;

&lt;p&gt;Does this mean that we should we go back to bare metal servers like in the old days? Not quite. We can still enjoy many of the benefits of the cloud, like horizontal scalability and no-outage upgrades using containers in combination with an orchestration platform. On that note, 37signals recently released &lt;a href="https://mrsk.dev"&gt;MRSK&lt;/a&gt;, a tool that allowed them to &lt;a href="https://world.hey.com/dhh/we-have-left-the-cloud-251760fb"&gt;to completely leave the cloud&lt;/a&gt;, saving them money and improving performance for their users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MRSK?
&lt;/h2&gt;

&lt;p&gt;MRSK is a deployment tool for containerized applications. It uses Docker containers to run the software and Traefik, a reverse proxy and load manager, to perform rolling updates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2SBy8jaA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/1db846d502cd12113336e4f5f8249775a87c364bf936c9f9eb3561a2907b50b0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2SBy8jaA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/1db846d502cd12113336e4f5f8249775a87c364bf936c9f9eb3561a2907b50b0.jpg" alt="Diagram showing the typical usage of an MRSK-deployed application. We have an application server running a Traefik instance as a reverse proxy. Behind the proxy, we run the application container. Users connect to Traefik, which routes the traffic to the application." width="616" height="292"&gt;&lt;/a&gt;&lt;br&gt;
The application container runs behind an instance of Traefik running as a reverse proxy. User requests are routed to the active application container.&lt;/p&gt;

&lt;p&gt;MRSK keeps things simple by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using standard Docker images.&lt;/li&gt;
&lt;li&gt;Connecting to the servers via SSH.&lt;/li&gt;
&lt;li&gt;Giving each server a single role.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point gives us the first clue about how MRSK works. It assumes a multi-server setup, where every machine fulfills only one role. In other words, if your application requires a database, MRSK expects to have at least two servers: one for the application and one for the database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g-6hwnmn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/559fac6b8f5d0bf517f06cd7e6dab47a6ffe5be8af2be1c85f2f7a943f0a4694.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g-6hwnmn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/559fac6b8f5d0bf517f06cd7e6dab47a6ffe5be8af2be1c85f2f7a943f0a4694.jpg" alt="The diagram shows two servers. The user's browser accesses the Application container in the application server via a Traefik proxy running on the same machine. The application server connects with a second server hosting the database in a Docker container. The developer orchestrates all changes using MRSK." width="800" height="574"&gt;&lt;/a&gt;&lt;br&gt;
Deploying the application and database with a single MRSK command.&lt;/p&gt;

&lt;p&gt;MRSK cares about your servers and little else. Load balancing is limited to the server level. MRSK uses Traefik to forward HTTP traffic to every container running on a machine. If you want to do horizontal scaling, you'll need to put a separate load balancer in front of everything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7LgXut8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e3d337f45cd63879d491116cd5f0467398c5b5d3af57e61bfb802db3be1f8a02.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7LgXut8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e3d337f45cd63879d491116cd5f0467398c5b5d3af57e61bfb802db3be1f8a02.jpg" alt="This is the same diagram as before, but in this case, we have two application servers, each with its own Traefik proxy running on the same machine. Because MRSK does not do load balancing when multiple servers are involved, we need to add a load balancer in front of both application servers." width="800" height="610"&gt;&lt;/a&gt;&lt;br&gt;
MRSK does not manage multi-server load balancing. You need to add your own.&lt;/p&gt;

&lt;p&gt;Because MRSK doesn't care where the application runs. You can use a cloud server, on-premise machines, or even run VMs on your laptop to test drive the tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started with MRSK
&lt;/h2&gt;

&lt;p&gt;You'll need the following to build and deploy an application with MRSK:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Docker Hub account or similar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A software project with its Dockerfile.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Two servers with SSH access.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: will need at least one server for each role. A deployment will typically need at least two machines: one for the application and one for the database. &lt;strong&gt;You cannot host the database on the same machine as the application.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Ensure you have SSH access&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before starting, ensure that you have SSH access to the deployment server. It can be any cloud, bare metal server, or even a VM on your laptop. What matters is that it already has your SSH key authorized for root access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ ssh root@SERVER_IP_ADDRESS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that doesn't work, but you know the root's password, you can add your key with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ ssh-copy-id root@SERVER_IP_ADDRESS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing MRSK
&lt;/h3&gt;

&lt;p&gt;So, now that we have our server, we can install MRSK on your local machine. Either as a Ruby Gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;mrsk &lt;span class="nt"&gt;-v&lt;/span&gt; 0.13.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, you can try the ready-made official Docker image by aliasing the command in your shell like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;mrsk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/mrsked/mrsk:v0.13.2'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since MRSK is in active development and quite new, I suggest picking a version and sticking with it to prevent updates from breaking your deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring MRSK
&lt;/h3&gt;

&lt;p&gt;For this next step, we will need a project to deploy. MRSK works with any codebase as long as it includes a Dockerfile. For this tutorial, I will be using the following demo project, which exposes an API-based addressbook with PostgreSQL for persistence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/TomFern/dockerizing-nodejs"&gt;TomFern/dockerizing-nodejs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the project's root folder, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config/deploy.yml&lt;/code&gt;: the main configuration file. It declares how to deploy the application to all your servers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt;: for sensitive environment variables, including passwords and tokens. It should be added to &lt;code&gt;.gitignore&lt;/code&gt; so it's never checked in the repository.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.mrsk/hooks&lt;/code&gt;: contains Git-like hooks. We won't be using any of these features in this tutorial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The building block of MRSK deployments is called &lt;strong&gt;applications&lt;/strong&gt;, which are containers running in one or more servers. They are connected to the Internet via &lt;a href="https://traefik.io/traefik/"&gt;Traefik&lt;/a&gt;, a reverse proxy.&lt;/p&gt;

&lt;p&gt;Let's configure our deployment. To do that, open deploy.yml and edit the following values near the top of the file. Uncomment the lines as needed. You will, at the very least, need to define the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A name for the application.&lt;/li&gt;
&lt;li&gt;The server IP address. This will be your application server.&lt;/li&gt;
&lt;li&gt;The Docker image name without the tag.&lt;/li&gt;
&lt;li&gt;Your Docker Hub username (you can also use a different registry).&lt;/li&gt;
&lt;li&gt;In the registry.password add "DOCKER_PASSWORD". This is a reference to a variable defined in .env.&lt;/li&gt;
&lt;li&gt;Environment variables for the application. The values under clear are stored in plaintext. Passwords should be stored in .env, and their variable name should be listed under secret in the config file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm configuring an "addressbook" application in the example below. Its image name is TomFern/addressbook (notice the lack of tags, which are handled by MRSK). My application uses a PostgreSQL database, for which I set &lt;code&gt;DB_HOST&lt;/code&gt; to the IP address of the db and reference the variable containing the password in &lt;code&gt;.env&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;addressbook&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tomfern/addressbook&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy to these servers.&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;216.238.101.228&lt;/span&gt;

&lt;span class="c1"&gt;# Credentials for your image host.&lt;/span&gt;
&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tomfern&lt;/span&gt;
 &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOCKER_PASSWORD&lt;/span&gt;

&lt;span class="c1"&gt;# Environment variables&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;216.238.113.141&lt;/span&gt;
 &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MRSK can handle containerized dependencies like databases, caches, or search services. MRSK calls them &lt;strong&gt;accessories&lt;/strong&gt;. If you need to define an accessory, scroll down the config file until you find the &lt;code&gt;accessories&lt;/code&gt; section. Then, set the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The db engine image name with its tag, e.g. &lt;code&gt;postgres:14&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The db server IP address and the database port.&lt;/li&gt;
&lt;li&gt;Any secrets or environment variables needed for database initialization.&lt;/li&gt;
&lt;li&gt;One or more volume mappings for data persistence — so data is not wiped out when the container stops.&lt;/li&gt;
&lt;li&gt;You can also define startup scripts to run during database initialization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following snippet shows how to configure a PostgreSQL database accessory for our demo app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
 &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;216.238.113.141&lt;/span&gt;
 &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
 &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
 &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open the &lt;code&gt;.env&lt;/code&gt; file. Fill in the password of your Docker Hub account and for the "Postgres" user in your PostgreSQL server. The value of every secret environment variable defined in &lt;code&gt;deploy.yml&lt;/code&gt; must be set here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOCKER_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_DOCKER_HUB_PASSWORD
&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;THE_POSTGRES_ADMIN_PASSWORD
&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;THE_ADDRESSBOOK_APP_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may use the same password for &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; and &lt;code&gt;DB_PASSWORD&lt;/code&gt; or create a dedicated user for the app in the database. If you do so, you'll also need to define &lt;code&gt;DB_USER&lt;/code&gt; in the config file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;addressbook&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tomfern/addressbook&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;216.238.113.141&lt;/span&gt;
 &lt;span class="na"&gt;DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_username&lt;/span&gt;
 &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a healthcheck route
&lt;/h3&gt;

&lt;p&gt;By default, MRSK checks that the application container is up by running curl on the &lt;code&gt;/up&lt;/code&gt; route. You can change the healthcheck endpoint in the config file.&lt;/p&gt;

&lt;p&gt;Since the demo does not have a healthcheck route, let's add one. In &lt;code&gt;app.js&lt;/code&gt;, add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.js&lt;/span&gt;

&lt;span class="c1"&gt;// ....&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;healthRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;healthRouter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And create a new file called &lt;code&gt;routes/health.js&lt;/code&gt; with the following content, which checks if the app can connect with the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/health.js&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})))&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MRSK uses curl inside the container to perform the healthcheck, so ensure that your Dockerfile installs the tool. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18.16.0-alpine3.17&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add curl

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prepare servers and deploy
&lt;/h3&gt;

&lt;p&gt;We're set to begin with the deployment. MRSK can do everything with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk setup

Acquiring the deploy lock
Ensure curl and Docker are installed...
Log into image registry...
Build and push app image...
Ensure Traefik is running...
Start container with version c439617 using a 7s readiness delay &lt;span class="o"&gt;(&lt;/span&gt;or reboot &lt;span class="k"&gt;if &lt;/span&gt;already running&lt;span class="o"&gt;)&lt;/span&gt;...
Releasing the deploy lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installs Docker on all machines.&lt;/li&gt;
&lt;li&gt;Starts Traefik in the app server.&lt;/li&gt;
&lt;li&gt;Starts the PostgreSQL container in the database server.&lt;/li&gt;
&lt;li&gt;Builds the Docker image for your application on your laptop and uploads it to Docker Hub.&lt;/li&gt;
&lt;li&gt;Pulls the image and starts the application on the server.&lt;/li&gt;
&lt;li&gt;Routes inbound traffic into the application container.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MRSK runs a health check to verify that the application is ready to work before being exposed to the Internet. Once the check passes, Traefik routes traffic into the app's container.&lt;/p&gt;

&lt;p&gt;By default, the health check is a GET request to the &lt;code&gt;/up&lt;/code&gt; route (expecting status code 200), but you can change that in &lt;code&gt;deploy.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iE2CBnbp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e57ea7c11dbe8e7e6d206de3a2107cda0495187d2aeb89f90865dd76b32ac08f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iE2CBnbp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e57ea7c11dbe8e7e6d206de3a2107cda0495187d2aeb89f90865dd76b32ac08f.jpg" alt="Deployment diagram. The developer runs 'mrsk build' to build the image, push it to Docker Hub and run it on the server. The application server has two containers running: the application and a Traefik instance running in reverse proxy mode. Traffic is routed to the application once the health check passes." width="800" height="416"&gt;&lt;/a&gt;&lt;br&gt;
MRSK deployment mechanism. The image is built into the developer's machine, uploaded to Docker Hub, and pulled into the server. Then, a health check ensures it started correctly. Once verified, Traefik routes traffic into the application container.&lt;/p&gt;

&lt;p&gt;We can check what containers are running with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk details

Traefik Host: 216.238.101.228
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5d08d56b760b traefik:v2.9 &lt;span class="s2"&gt;"/entrypoint.sh --pr…"&lt;/span&gt; 2 minutes ago Up 2 minutes 0.0.0.0:80-&amp;gt;80/tcp, :::80-&amp;gt;80/tcp traefik

App Host: 216.238.101.228
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
28acfd2cda02 tomfern/addressbook:3ecd87430ab7ab6cc30a1542784ddb75fbfd8e74 &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt; 2 minutes ago Up 2 minutes 3000/tcp addressbook-web-3ecd87430ab7ab6cc30a1542784ddb75fbfd8e74


CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a843852686b6 postgres:14 &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt; 2 minutes ago Up 2 minutes 0.0.0.0:5432-&amp;gt;5432/tcp, :::5432-&amp;gt;5432/tcp addressbook-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, MRSK tags every built image with a unique identifier, allowing us to track changes, rolling back and forwards as needed.&lt;/p&gt;

&lt;p&gt;Before testing the application, we should run any database migration/setup scripts to initialize its contents. MRSK allows us to run commands in running containers with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk app &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"npm run migrate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can test the application, which should be ready to receive user requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT http://216.238.101.228/persons &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
"id": 1,
"firstName": "David",
"lastName": "Bowie"
}'&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl 216.238.101.228/persons/all
&lt;span class="o"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"firstName"&lt;/span&gt;: &lt;span class="s2"&gt;"David"&lt;/span&gt;,
 &lt;span class="s2"&gt;"lastName"&lt;/span&gt;: &lt;span class="s2"&gt;"Bowie"&lt;/span&gt;,
 &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1,
 &lt;span class="s2"&gt;"updatedAt"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-04-30T22:44:29.115Z"&lt;/span&gt;,
 &lt;span class="s2"&gt;"createdAt"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-04-30T22:44:29.115Z"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploying updates with MRSK
&lt;/h3&gt;

&lt;p&gt;Let's make a change to the application so we can see how MRSK handles updates. In the demo code, we have a &lt;code&gt;/persons&lt;/code&gt; API endpoint; it would be a good idea to version it. So let's change this line in &lt;code&gt;app.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/persons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;personsRouter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the line so the base endpoint route is &lt;code&gt;/persons/v1/&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/persons/v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;personsRouter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fastest way to update the container is with &lt;code&gt;mrsk redeploy&lt;/code&gt;, which does away with a few tasks like checking whether Docker is installed in all servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk redeploy

Acquiring the deploy lock
Running docker buildx build
Running docker image &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt; tomfern/addressbook:7db892 on 216.238.101.228
Running docker pull tomfern/addressbook:latest on 216.238.101.228
Health check against /up succeeded with 200 OK!
Finished all &lt;span class="k"&gt;in &lt;/span&gt;35.8 seconds
Releasing the deploy lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The redeploy command will rebuild the image, upload it to Docker Hub and start it on the application server. As soon as the health check passes, the traffic is routed to the new version, and the old container shuts down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5hERl8Un--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e374025fe35e3481250c6a7a2990caa0ede768e84edb26eeb413d71832d75d73.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5hERl8Un--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e374025fe35e3481250c6a7a2990caa0ede768e84edb26eeb413d71832d75d73.jpg" alt="Redeployment mechanism. Same as in the last diagram, but this time, we have two application container instances, the old and the new. Once the health check passes on the new instance, the traffic is switched over, and the old container is stopped." width="800" height="514"&gt;&lt;/a&gt;&lt;br&gt;
MRSK redeploy mechanism. A new instance of the application is built and deployed. Once its health check passes, traffic is routed to the new instance, and the old one is shut down.&lt;/p&gt;

&lt;p&gt;After deployment, we can check that the new route is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl 216.238.101.228/persons/v1/all

&lt;span class="o"&gt;[&lt;/span&gt;
 &lt;span class="o"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1,
 &lt;span class="s2"&gt;"firstName"&lt;/span&gt;: &lt;span class="s2"&gt;"David"&lt;/span&gt;,
 &lt;span class="s2"&gt;"lastName"&lt;/span&gt;: &lt;span class="s2"&gt;"Bowie"&lt;/span&gt;,
 &lt;span class="s2"&gt;"createdAt"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-05-07T17:41:45.580Z"&lt;/span&gt;,
 &lt;span class="s2"&gt;"updatedAt"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-05-07T17:41:45.580Z"&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rolling back updates with MRSK
&lt;/h3&gt;

&lt;p&gt;MRSK gives us a safe path for rolling back updates. If the new version is causing trouble, we can return to the last working version with a single command.&lt;/p&gt;

&lt;p&gt;To rollback, first, we need to find which images are available on the server with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk app containers

App Host: 216.238.101.228

CONTAINER ID   IMAGE                                                          COMMAND                  CREATED         STATUS                     PORTS      NAMES

f648fec5f604   tomfern/addressbook:66347a86f8a123e35492dd43463540c23f7db892   &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt;   4 minutes ago   Up 4 minutes               3000/tcp   addressbook-web-66347a86f8a123e35492dd43463540c23f7db892

b3b1d13b8a1c   9500e07b6387                                                   &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt;   9 minutes ago   Exited &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt; 4 minutes ago              addressbook-web-66347a86f8a123e35492dd43463540c23f7db892_05d41f3ba39d2b1b

ce4a5c31e6fc   tomfern/addressbook:f043325e3984ec245a94b21bd236afcc537a9739   &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt;   3 hours ago     Exited &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt; 9 minutes ago              addressbook-web-f043325e3984ec245a94b21bd236afcc537a9739
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that the previous version was tagged as "f043325e3984ec245a94b21bd236afcc537a9739". We can run &lt;code&gt;mrsk rollback&lt;/code&gt; to go back to it. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mrsk rollback f043325e3984ec245a94b21bd236afcc537a9739
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the container does not exist on the server — MRSK routinely prunes older images — you can always look up the last good version in Docker Hub or by checking the logs in your CI/CD platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0_tffdA6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/df43d3999784f6d9f62fff66e22b7b781c8ece70a5eb46cfa9d1f05e7144450f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0_tffdA6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/df43d3999784f6d9f62fff66e22b7b781c8ece70a5eb46cfa9d1f05e7144450f.jpg" alt="Rollback diagram. This diagram is like the last one but in reverse. The old instance starts from the Docker cache, and traffic is routed to the old version." width="800" height="432"&gt;&lt;/a&gt;&lt;br&gt;
MRSK rollback mechanism. The old version is restarted and health-checked. Once working, traffic is switched over to the old version, and the new container stops.&lt;/p&gt;

&lt;p&gt;Once you're done with your application, you can remove everything, including the database, with mrsk remove. This will destroy the database, remove Docker and stop all applications.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;MRSK’s limitations&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;MRSK development is ongoing, so we may expect behavior changes and new breaking features in the future. But do not mistake its newness for immaturity; 37signals has reportedly begun using it to &lt;a href="https://world.hey.com/dhh/introducing-mrsk-9330a267"&gt;move away from the cloud with great success already&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That being said, there are a few things MRSK cannot do, and that makes it a bad fit for some use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The default behavior is to expose the application's HTTP port without SSL. You are supposed to set up an SSL terminator, a load balancer, or a CDN in front of the whole deployment.&lt;/li&gt;
&lt;li&gt;Containers cannot communicate between themselves within the same server. This is &lt;a href="https://github.com/mrsked/mrsk/issues/41"&gt;by design&lt;/a&gt;. You can always configure Docker networks server-side manually. But it's better to stick to one server per role, so putting the application and the database on the same machine is a bad idea.&lt;/li&gt;
&lt;li&gt;You will need to configure a firewall in front of your whole setup to ensure users can only access the web application.&lt;/li&gt;
&lt;li&gt;If you want to distribute the load among many servers, you should set up a load balancer in front of your servers. MRSK does not provide this feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7LgXut8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e3d337f45cd63879d491116cd5f0467398c5b5d3af57e61bfb802db3be1f8a02.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7LgXut8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e3d337f45cd63879d491116cd5f0467398c5b5d3af57e61bfb802db3be1f8a02.jpg" alt="In this diagram, we have two application servers, each with its own Traefik proxy running on the same machine. Because MRSK does not do load balancing when multiple servers are involved, we need to add a load balancer in front of both application servers." width="800" height="610"&gt;&lt;/a&gt;&lt;br&gt;
MRSK is designed for each server to have one role and does not provide load balancing or SSL termination. You need to handle it by yourself.&lt;/p&gt;

&lt;p&gt;At the end of the day, MRSK offers a simplified workflow — especially when compared with beasts like Kubernetes — by not trying to handle every aspect of the system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying applications with MRSK and CI/CD
&lt;/h2&gt;

&lt;p&gt;MRSK can be easily integrated into &lt;a href="https://semaphoreci.com/cicd"&gt;Continuous Integration and Delivery&lt;/a&gt; to get automated deployments. This section will show how to configure a Semaphore CI/CD workflow for continuous deployment. If you've never used Semaphore, check out our &lt;a href="https://docs.semaphoreci.com/guided-tour/getting-started/"&gt;getting started guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we'll need to configure a couple of &lt;a href="https://docs.semaphoreci.com/essentials/using-secrets/"&gt;secrets&lt;/a&gt;. Secrets are Semaphore's way of storing sensitive data such as passwords or API keys.&lt;/p&gt;

&lt;p&gt;Log in or create an account on &lt;a href="https://semaphoreci.com"&gt;Semaphore&lt;/a&gt;. Then, go to your organization in the top right corner and select &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Use the &lt;strong&gt;New Secret&lt;/strong&gt; button to create a secret called "mask-deploy". Set the values as follows. - &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt;: with the database "postgres" password. - Upload your SSH keys (public and private) to the path &lt;code&gt;/home/semaphore/.ssh/&lt;/code&gt; as shown in the picture below. These are needed for Semaphore to access your servers via SSH.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GkJVsVBO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/353f391b9111b015c0fee90a8b2e2a97e0c330c71f8036bbffeef2a2247d710b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GkJVsVBO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/353f391b9111b015c0fee90a8b2e2a97e0c330c71f8036bbffeef2a2247d710b.jpg" alt="Editing a secret called mask-deploy. It defines a variable called POSTGRES_PASSWORD and two SSH key files in the path /home/semaphore/.ssh" width="499" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create a second secret called "dockerhub" with your Docker Hub credentials, as shown below. This is required to push images into the Docker Registry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jH6pSlLN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/ecaed6469894efd6f578723b57526f6b261c8062ed421a2a500fefff5fd21662.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jH6pSlLN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/ecaed6469894efd6f578723b57526f6b261c8062ed421a2a500fefff5fd21662.jpg" alt="Creating a dockerhub secret. We define two variables: DOCKER_USERNAME and DOCKER_PASSWORD" width="471" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now open or add your project in Semaphore. This will connect Semaphore to your repository. If you don't already have a CI pipeline, you can select one of the examples or create your own.&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;+Add Promotion&lt;/strong&gt; to create a continuous deployment pipeline. Select the new pipeline and type the following commands in the &lt;strong&gt;Prologue&lt;/strong&gt;. This pins the MRSK and Ruby versions, so updates keep our automated deployment intact.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TIoYofEJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e08c3525cfc05ee692dcf13b67741579f1c88a8214aa02186483639dd9d1dc49.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TIoYofEJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e08c3525cfc05ee692dcf13b67741579f1c88a8214aa02186483639dd9d1dc49.jpg" alt="The workflow visual editor in Semaphore shows an empty pipeline. On the right side, we define commands in the global prologue." width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on &lt;strong&gt;Add Block&lt;/strong&gt; and type the following commands in the job. Then, enable the "dockerhub" and "mask-deploy" secrets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | docker login &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_USERNAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;
checkout
mrsk build create
mrsk redeploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wIZR-48O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/8dcd6c1857c179e861e9dbc8eba58a13c1b578dd990c67ddc4c756a108c299f7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wIZR-48O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/8dcd6c1857c179e861e9dbc8eba58a13c1b578dd990c67ddc4c756a108c299f7.jpg" alt="The workflow visual editor in Semaphore shows a block with a single job called deploy. It contains the deployment commands and has the mrsk-deploy and dockerhub secrets enabled." width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we'll create a job to migrate the database, in case the new version includes schema changes. Select &lt;strong&gt;Add Block&lt;/strong&gt; and type the following commands in the job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;checkout
mrsk app &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--reuse&lt;/span&gt; &lt;span class="s2"&gt;"npm run migrate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LMAw9j9d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/6c149d4f1b527fa1bc9eee5567f68c3b99c1edb90c409051ff0ab56838611f0a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LMAw9j9d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/6c149d4f1b527fa1bc9eee5567f68c3b99c1edb90c409051ff0ab56838611f0a.jpg" alt="The workflow visual editor in Semaphore shows the migrate block. It shows the migrate commands and has the mrsk-deploy secret enabled" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enable the "mask-deploy" secret and select &lt;strong&gt;Run the workflow&lt;/strong&gt;. Wait for the CI pipeline to be done. Then, press the &lt;strong&gt;Deploy to Prod&lt;/strong&gt; button to run the deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r5KDo3I1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/a40f5b7bf31f752a5090887b575b3bcbfbf5169e8287a419d47c06e4c9673ca3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r5KDo3I1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/a40f5b7bf31f752a5090887b575b3bcbfbf5169e8287a419d47c06e4c9673ca3.jpg" alt="The final deployment pipeline with two jobs: Deploy and Migrate" width="558" height="236"&gt;&lt;/a&gt;&lt;br&gt;
The final continuous deployment pipeline.&lt;/p&gt;

&lt;p&gt;And we're done! Congratulations on automating your deployment with CI/CD and MRSK. You can see the final state of the code and pipeline in the "mrsk" branch of the demo repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/TomFern/dockerizing-nodejs/tree/mrsk"&gt;TomFern/dockerizing-nodejs&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;If you experience issues with deployment try the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the healthcheck fails, ensure tht curl is installed in the application container image.&lt;/li&gt;
&lt;li&gt;If it still fails after ensuring curl is installed, check that connectivity works between the application and the database. You may need to configure a VPS or set firewall rules.&lt;/li&gt;
&lt;li&gt;You can try starting the images manually by running the docker exec commands in the machines to see the output of the logs. These will help you find the root cause of the problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you get an error message stating that there is a lock, run mrsk lock release. This can happen when MRSK fails during deployment and the admin lock is not released.&lt;/p&gt;

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

&lt;p&gt;MRSK is minimalistic to the point of elegance. By combining traditional servers with the flexibility of containers, it allows us to run our containerized services with ease on any server.&lt;/p&gt;

&lt;p&gt;MRSK presents a compelling solution if you're seeking to optimize your software deployments, reduce vendor lock-in, and maintain flexibility in choosing deployment environments while enjoying the benefits of cloud-native architectures.&lt;/p&gt;

</description>
      <category>containers</category>
      <category>docker</category>
      <category>devops</category>
      <category>news</category>
    </item>
    <item>
      <title>A First Look at Neon: A Postgres Database That Branches</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Mon, 06 Mar 2023 21:25:05 +0000</pubDate>
      <link>https://forem.com/semaphore/a-first-look-at-neon-a-postgres-database-that-branches-10e6</link>
      <guid>https://forem.com/semaphore/a-first-look-at-neon-a-postgres-database-that-branches-10e6</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@kpzhnv?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Damir Kopezhanov&lt;/a&gt; on &lt;a href="https://unsplash.com/wallpapers/colors/neon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Relational databases have a long, long history. The first appeared in the 1970s, and while the technology has certainly evolved, the relational model has &lt;a href="https://db-engines.com/en/ranking" rel="noopener noreferrer"&gt;proved the most popular over the last 5 decades&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Is there room for innovation after 50 years of history? The folks at Neon are showing us that one &lt;em&gt;can&lt;/em&gt;, in fact, teach an old dog new tricks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Neon?
&lt;/h2&gt;

&lt;p&gt;Neon is an &lt;a href="https://github.com/neondatabase/neon" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; (Apache 2.0) alternative to &lt;a href="https://aws.amazon.com/rds/aurora/features/" rel="noopener noreferrer"&gt;AWS Aurora&lt;/a&gt; or Google's &lt;a href="https://cloud.google.com/sql/docs/postgres" rel="noopener noreferrer"&gt;Cloud SQL&lt;/a&gt; for Postgres. Neon is a serverless, scalable implementation of PostgreSQL that you can run on-premise or try through its &lt;a href="https://neon.tech" rel="noopener noreferrer"&gt;managed service&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Neon decomposes the PostgreSQL architecture into two layers: compute and storage. The compute layer consists of stateless PostgreSQL running on Kubernetes, allowing pods to be scaled on demand — even to zero.&lt;/p&gt;

&lt;p&gt;Persistence is achieved with the &lt;em&gt;Neon storage engine&lt;/em&gt;, a custom-made layer that handles both transactions and data. The transaction log is processed through a set of redundant &lt;em&gt;safekeeper&lt;/em&gt; services, while data pages are managed on disk by the &lt;em&gt;pageserver&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fcf3d1832aea430075000e7e847c4abdf033159a985a2c3a2876a642d26439a66.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fcf3d1832aea430075000e7e847c4abdf033159a985a2c3a2876a642d26439a66.jpg" alt="An achitecture diagram. Many compute nodes are running as pods in a Kubernetes cluster. The nodes are part of the compute plane. The plane connects with the storage plane in two ways: first, compute nodes connect to a pageserver. The page server then stores the data in the storage backend. Second, compute nodes communicate with safekeeper nodes, which deal with the transaction log (WAL) stream."&gt;&lt;/a&gt;&lt;br&gt;
Neon architecture splits the database into scalable compute and storage planes. Compute nodes can be started and stopped depending on demand.&lt;/p&gt;

&lt;p&gt;Currently, the managed service is running a free &lt;a href="https://neon.tech/docs/introduction/technical-preview-free-tier/" rel="noopener noreferrer"&gt;tech preview&lt;/a&gt; with some limitations that we'll discuss later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Neon's killer feature: branches
&lt;/h2&gt;

&lt;p&gt;Neon was &lt;a href="https://neon.tech/blog/hello-world/" rel="noopener noreferrer"&gt;launched in June 2021&lt;/a&gt;. Being a new project, the managed service may have fewer features than the competition. But Neon has one feature that, to my knowledge, no one else has: branches.&lt;/p&gt;

&lt;p&gt;Every developer is familiar with branches. In Neon, branches work pretty much the same as in Git, except they cannot be merged (although there are plans to add schema-based merging in the future). You can, at any point, branch off the main trunk, effectively creating an “alternate timeline".&lt;/p&gt;

&lt;p&gt;Since branches in Neon are writable, this feature allows us to do things no other database engine can do. For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Freely experiment without impacting the main branch.&lt;/li&gt;
&lt;li&gt;Instantly back up the database. So, if data is lost by mistake, we can switch to the last good branch.&lt;/li&gt;
&lt;li&gt;Simplify &lt;a href="https://semaphoreci.com/blog/integration-tests" rel="noopener noreferrer"&gt;integration testing&lt;/a&gt;. Developers can run tests in disposable test-specific branches.&lt;/li&gt;
&lt;li&gt;Safely try out &lt;a href="https://semaphoreci.com/blog/database-management" rel="noopener noreferrer"&gt;automated database migrations&lt;/a&gt; on production.&lt;/li&gt;
&lt;li&gt;Run analytics or machine learning workloads in isolation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instantly duplicate all the databases that serve to a specific cluster of &lt;a href="https://semaphoreci.com/blog/microservice-architecture" rel="noopener noreferrer"&gt;microservices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can't do any of these things on traditional database engines. Not easily at least. Some database engines like SQL Server have snapshots, which indeed can create instant copies of a database. But snapshots are read-only and this limits their utility. On most database engines, we have to resort to clunkier mechanisms like backup and restore or replication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fe6d8617070e74da96f90e09bb2bcbdb3c7e99ebae0e8e92eead0047cee1c3bdb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fe6d8617070e74da96f90e09bb2bcbdb3c7e99ebae0e8e92eead0047cee1c3bdb.jpg" alt="The diagram shows 4 branches. Three branches split from the main one. They are called 'test A', 'test B', and 'test C'. The branches have the contents of the database at the branching point. From that moment on, they follow their own timelines and can differ from each other and the main branch."&gt;&lt;/a&gt;&lt;br&gt;
A few use cases for Neon branches.&lt;/p&gt;

&lt;p&gt;Branches are per-project. And a project can have multiple databases. That means that creating a branch duplicates all the databases in that project. We can take advantage of this &lt;a href="https://neon.tech/docs/manage/overview/" rel="noopener noreferrer"&gt;project &amp;gt; database hierarchy&lt;/a&gt; to clone a group of related databases in one operation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started with Neon
&lt;/h2&gt;

&lt;p&gt;Let's try out Neon's managed service. To create a tech preview free account, just follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://neon.tech/sign_in" rel="noopener noreferrer"&gt;neon.tech/sign_in&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create a project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Download env.txt&lt;/strong&gt;. This file contains everything you need to connect to the database instance.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Settings&lt;/strong&gt; and copy the project id.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also need to generate an API Key, as shown below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on your avatar and select &lt;strong&gt;Account&lt;/strong&gt; &amp;gt; &lt;strong&gt;Developer Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create new API key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Edit &lt;code&gt;env.txt&lt;/code&gt; and add the following lines:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;export NEON_API_KEY=Your-API-Key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;export PROJECT_ID=Your-Project-ID&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add the keyword &lt;code&gt;export&lt;/code&gt; before every variable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final &lt;code&gt;env.txt&lt;/code&gt; file should look like this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Connection details&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PGHOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ep-random-name.us-east-2.aws.neon.tech
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PGDATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;neondb
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Tommy
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sekret1

&lt;span class="c"&gt;# Connection string&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://Tommy:sekret1@ep-random-name.us-east-2.aws.neon.tech/neondb

&lt;span class="c"&gt;# Neon config&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NEON_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MyApiKey
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NEON_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;random-name-140532
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll need this file to connect to the Neon database and API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Neon UI
&lt;/h2&gt;

&lt;p&gt;The Neon dashboard includes an SQL editor to run commands and controls for creating branches or endpoints.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fb227ef90cf1057175cfcc306eda7e73be346f59b5c44909fcdcef8e30da961e6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Fb227ef90cf1057175cfcc306eda7e73be346f59b5c44909fcdcef8e30da961e6.jpg" alt="Screenshot of the Neon dashboard managed service at console.neon.tech."&gt;&lt;/a&gt;&lt;br&gt;
The managed service dashboard.&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;Branches&lt;/strong&gt; page, we’ll find options for creating a new branch. Here, you can select the &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;when&lt;/em&gt;. You must choose the parent branch and how much data to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Head&lt;/strong&gt;: the new branch is a copy of the current database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt;: the branch has the parent's data up to a specified date and time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LSN&lt;/strong&gt;: the branch has the parent's data up to a specified log sequence number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Ff1a10f3958d2848814a5b609393c596e95e05ed84b9398e9d096b1f0e51474d9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftomfern%2Fblog-assets%2Fmaster%2Fimages%2Ff1a10f3958d2848814a5b609393c596e95e05ed84b9398e9d096b1f0e51474d9.jpg" alt="Screenshot of the create branch UI. It shows a parent branch selector, three options to create the branch: head, time, and LSN, and the option to create an endpoint for the new branch."&gt;&lt;/a&gt;&lt;br&gt;
The create branch UI.&lt;/p&gt;

&lt;p&gt;Endpoints for the branches can be created on the same screen or on the &lt;strong&gt;Endpoints&lt;/strong&gt; page.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connecting to Neon
&lt;/h2&gt;

&lt;p&gt;Neon is a PostgreSQL database, so we'll need to install the client tools. Check which version yours is running with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;--version&lt;/span&gt;
psql &lt;span class="o"&gt;(&lt;/span&gt;PostgreSQL&lt;span class="o"&gt;)&lt;/span&gt; 15.1 &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu 15.1-1.pgdg20.04+1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neon works best with versions 14 and 15 of the client tools. So, if needed, head to &lt;a href="https://www.postgresql.org/download/" rel="noopener noreferrer"&gt;postgresql.org/download&lt;/a&gt; to get the latest release.&lt;/p&gt;

&lt;p&gt;Now, let's source the &lt;code&gt;env.txt&lt;/code&gt; and try connecting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;env.txt
&lt;span class="nv"&gt;$ &lt;/span&gt;psql

psql &lt;span class="o"&gt;(&lt;/span&gt;15.1 &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu 15.1-1.pgdg20.04+1&lt;span class="o"&gt;)&lt;/span&gt;, server 14.6&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;neondb&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; SELECT version&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 version
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------&lt;/span&gt;
 PostgreSQL 14.6 on x86_64-pc-linux-gnu, compiled by gcc &lt;span class="o"&gt;(&lt;/span&gt;Debian 10.2.1-6&lt;span class="o"&gt;)&lt;/span&gt; 10.2.1 20210110, 64-bit
&lt;span class="o"&gt;(&lt;/span&gt;1 row&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also do browser-based authentication by running the command shown below. This will open a browser window and let you select the project you want to connect to.&lt;/p&gt;

&lt;p&gt;$ psql -h pg.neon.tech&lt;/p&gt;

&lt;p&gt;We may also test the Neon API key with curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://console.neon.tech/api/v2/projects'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$NEON_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"projects"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
 &lt;span class="o"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"calm-guy-140532"&lt;/span&gt;,
 &lt;span class="s2"&gt;"platform_id"&lt;/span&gt;: &lt;span class="s2"&gt;"aws"&lt;/span&gt;,
 &lt;span class="s2"&gt;"region_id"&lt;/span&gt;: &lt;span class="s2"&gt;"aws-us-east-2"&lt;/span&gt;,
 &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"test-project"&lt;/span&gt;,
 &lt;span class="s2"&gt;"provisioner"&lt;/span&gt;: &lt;span class="s2"&gt;"k8s-pod"&lt;/span&gt;,
 &lt;span class="s2"&gt;"pg_version"&lt;/span&gt;: 14,
 &lt;span class="s2"&gt;"locked"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;,
 &lt;span class="s2"&gt;"created_at"&lt;/span&gt;: &lt;span class="s2"&gt;"2022-12-22T18:16:13Z"&lt;/span&gt;,
 &lt;span class="s2"&gt;"updated_at"&lt;/span&gt;: &lt;span class="s2"&gt;"2022-12-22T18:16:13Z"&lt;/span&gt;,
 &lt;span class="s2"&gt;"proxy_host"&lt;/span&gt;: &lt;span class="s2"&gt;"us-east-2.aws.neon.tech"&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
 &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automated branching with CI/CD
&lt;/h2&gt;

&lt;p&gt;One of the most delicate parts of deployment is the database migration step because there is always the possibility of data loss. There &lt;a href="https://semaphoreci.com/blog/database-management" rel="noopener noreferrer"&gt;are several techniques to make this process safer&lt;/a&gt;. But with Neon, we can leverage branches to make the process infallible.&lt;/p&gt;

&lt;p&gt;You can check out a live example for running branches with continuous integration and delivery by &lt;a href="https://semaphoreci.com/blog/neon-database#automated-branching-with-ci-cd" rel="noopener noreferrer"&gt;checking out this tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical preview limitations
&lt;/h2&gt;

&lt;p&gt;At the time of writing Neon is in a &lt;a href="https://neon.tech/docs/introduction/technical-preview-free-tier/" rel="noopener noreferrer"&gt;technical preview&lt;/a&gt; stage. While the managed service is entirely free, it comes with some limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can only have one project per user. But a project can have multiple databases.&lt;/li&gt;
&lt;li&gt;A project can have up to nine branches in addition to the main branch.&lt;/li&gt;
&lt;li&gt;You can have up to three endpoints. One is always reserved for the main database. That leaves only two endpoints accessible for two other branches.&lt;/li&gt;
&lt;li&gt;The size limit is 3GB per branch on the free tier.&lt;/li&gt;
&lt;li&gt;Point-in-time branches can only go up to seven days into the past.&lt;/li&gt;
&lt;li&gt;There are no backup or restore options on the UI. The only alternative seems to run pg_dump neondb and take a remote backup.&lt;/li&gt;
&lt;li&gt;There are &lt;a href="https://neon.tech/docs/import/import-from-postgres/" rel="noopener noreferrer"&gt;a few observations around importing data&lt;/a&gt; from another PostgreSQL instance or a backup.&lt;/li&gt;
&lt;li&gt;There is a 100 concurrent connections limit. You can enable connection pooling to raise the limit to 1,000 connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news is that if you like the database, you can always run it on-premise or in your cloud of choice to remove these limitations.&lt;/p&gt;

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

&lt;p&gt;Neon's branching feature presents new options for development and database management. Even in its current technical preview stage, I can see great potential for this engine. Of course, we'll have to see how the project evolves, especially how the final pricing model will turn out.&lt;/p&gt;

&lt;p&gt;I hope you found this project interesting, and if so, you might want to &lt;a href="https://github.com/neondatabase/neon#join-the-development" rel="noopener noreferrer"&gt;consider contributing to Neon&lt;/a&gt;.&lt;/p&gt;

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

</description>
      <category>database</category>
      <category>newsql</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getting Started With Astro</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Fri, 03 Mar 2023 22:03:38 +0000</pubDate>
      <link>https://forem.com/semaphore/getting-started-with-astro-nb7</link>
      <guid>https://forem.com/semaphore/getting-started-with-astro-nb7</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@guanju223?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Ju Guan&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/astronaut?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting Started With Astro
&lt;/h1&gt;

&lt;p&gt;Web development, like most technologies, moves in waves and cycles. Static websites were all we had in the beginning. But pretty soon developers were hacking together the first server-generated sites thanks to Perl and PHP. This was the advance that would eventually kickstart frameworks like Laravel, Django, or Rails.&lt;/p&gt;

&lt;p&gt;Mobile devices would come to change how people consume the web. So long server-generated websites, hello client-rendered applications. The next wave brought frameworks that could give users a more app-like experience — without reloads — like React or AngularJS.&lt;/p&gt;

&lt;p&gt;But while single-page applications did provide a smoother experience, they have their drawbacks. Namely, longer page loads caused by all the extra JavaScript that has to be parsed and executed. Not to mention all the work it took to optimize for search engines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt; is a prime example of the current wave that we have been in since Next.js, taking us full circle: a web framework that combines server and client rendering to get the best of both worlds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Astro?
&lt;/h2&gt;

&lt;p&gt;Astro is an &lt;a href="https://github.com/withastro/astro"&gt;open-source&lt;/a&gt; framework for generating web applications on top of popular UI frameworks like React, Preact, Vue, or Svelte. An Astro page is composed of several independent components. To improve load times, Astro strips away all JavaScript and prerenders pages in the server unless developers mark a component as interactive, in which case Astro will send the minimum amount of JavaScript required for interactivity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bjAWlody--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e6b1ced46454cc27565cbe3c11d594313f2b859160b55c75dac62a3c07bd98e1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bjAWlody--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/e6b1ced46454cc27565cbe3c11d594313f2b859160b55c75dac62a3c07bd98e1.jpg" alt="A diagram showing the types of content Astro can generate: we have static HTML websites and server-rendered multi-page applications. The first kind can be hosted statically on platforms such as AWS S3, Google Cloud, GitHub, and GitLab pages or Firebase hosting. The latter can be hosted on platforms like Deno Deploy, Netlify, Vercel, or Cloudflare." width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;
Astro favors generating static and server-rendered sites without JavaScript in the browser. Developers can opt-in to client-side rendering for given routes or parts of the page.



&lt;p&gt;Thanks to this strategy, Astro pages load quickly, as no JavaScript needs to execute for the first render. In a process called hydration, Astro will “pour” JavaScript into components to make them dynamic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Astro?
&lt;/h2&gt;

&lt;p&gt;Astro stands out from the other web frameworks in many ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Speed&lt;/strong&gt;: Astro is statically and server-rendered when possible, making it an excellent fit for content-first, SEO-friendly websites like blogs, courseware, or marketing sites.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;UI agnostic&lt;/strong&gt;: unlike Next.js, which only works with React, or Nuxt.js–which requires Vue expertise, Astro doesn't force any UI framework on you. You can use none, roll your own, or use any of &lt;a href="https://docs.astro.build/en/guides/integrations-guide/#official-integrations"&gt;the many supported integrations&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Themes&lt;/strong&gt;: Astro has an impressive array of ready-made &lt;a href="https://astro.build/themes/"&gt;themes&lt;/a&gt; and &lt;a href="https://astro.new/"&gt;templates&lt;/a&gt; to get started quickly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Easy to pick up&lt;/strong&gt;: you don't need to learn React or Vue to get started with Astro. Its templating engine looks like plain HTML, and the code is cleanly separated from the presentation. You can pick an empty project and build it up at your own pace.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Batteries included&lt;/strong&gt;: Astro comes with the features we expect from a modern JavaScript/TypeScript framework like top-level awaits, Markdown and MDX support, and ESM includes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Islands&lt;/strong&gt;: the island architecture allows us to mix static content, server-rendered, and client-rendered components on the same page without conflicts, allowing us to build interactive sites with minimal overhead. We can even mix different frameworks in the same page, giving us yet another way of building &lt;a href="https://semaphoreci.com/blog/microfrontends"&gt;microfrontends&lt;/a&gt;. We'll see more about how islands work in a bit.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;To get started with Astro, install Node version 16.12.0 or higher and run the following command. Follow the on-screen wizard and choose to create an empty project when prompted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm create astro@latest

 astro   v1.9.1 Launch sequence initiated.

✔ Where would you like to create your new project? … awesome-website
✔ How would you like to setup your new project? › an empty project
✔ Template copied!
✔ Would you like to &lt;span class="nb"&gt;install &lt;/span&gt;npm dependencies? &lt;span class="o"&gt;(&lt;/span&gt;recommended&lt;span class="o"&gt;)&lt;/span&gt; … &lt;span class="nb"&gt;yes&lt;/span&gt;
✔ Packages installed!
✔ Would you like to initialize a new git repository? &lt;span class="o"&gt;(&lt;/span&gt;optional&lt;span class="o"&gt;)&lt;/span&gt; … &lt;span class="nb"&gt;yes&lt;/span&gt;
✔ Git repository created!
✔ How would you like to setup TypeScript? › Relaxed
✔ TypeScript settings applied!

  next   Liftoff confirmed. Explore your project!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you can start the website in developer mode by entering the directory of the project you just created and running:&lt;br&gt;
&lt;code&gt;npm run dev&lt;/code&gt; and visiting &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pages and routes
&lt;/h3&gt;

&lt;p&gt;The interesting stuff in Astro happens inside the &lt;code&gt;src&lt;/code&gt; folder. Checking what's there, we see a single directory called &lt;code&gt;pages&lt;/code&gt; with an &lt;code&gt;index.astro&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.astro.build/en/core-concepts/astro-pages/"&gt;Astro pages&lt;/a&gt; are a mix of HTML, Javascript, or TypeScript. This is the default &lt;code&gt;index.astro&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Astro.generator}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Astro&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Astro&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have noticed that Astro files start with fences delineated with &lt;code&gt;---&lt;/code&gt;. Whatever code we put inside these fences is executed on the server and prerendered before serving the page.&lt;/p&gt;

&lt;p&gt;Below the frontmatter, we find the content for this route, an augmented HTML form that allows for variables. We can, for example, define a variable in the frontmatter and use it in HTML like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/pages/index.astro
const title = "Astro";
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Astro.generator}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro uses a &lt;strong&gt;file-based routing&lt;/strong&gt;, so every file in the &lt;code&gt;pages&lt;/code&gt; folder is mapped to a route in the website. For example, if we create a file called &lt;code&gt;greetings.astro&lt;/code&gt;, we should see its contents at &lt;code&gt;http://localhost:3000/greetings&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    ---
    const greeting = "Hello, world!";
    ---

    &lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Astro.generator}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Astro&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{greeting}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to &lt;code&gt;.astro&lt;/code&gt; files, Astro can parse Markdown, MDX, JSX JavaScript, and TypeScript files. For instance, if we want to write a blog post in Markdown, we create a &lt;code&gt;post.md&lt;/code&gt; file under the &lt;code&gt;pages&lt;/code&gt; folder. Visiting the route will make Astro convert it into HTML on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Learning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Astro'&lt;/span&gt;
&lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2023-01-10&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;written&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Markdown.'&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tommy'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Learning Astro&lt;/span&gt;

This Markdown file should be rendered as HTML when I visit http://localhost:3000/post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.astro.build/en/core-concepts/astro-components/"&gt;Astro components&lt;/a&gt; are &lt;code&gt;*.astro&lt;/code&gt; files with reusable code and HTML. We can use components to write elements like headers, footers, navigation bars, buttons, and forms — anything that can be expressed as HTML can constitute a component.&lt;/p&gt;

&lt;p&gt;Let's create our first component in &lt;code&gt;src/components/Header.astro&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/components/Header.astro
---
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; 
&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Astro&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once defined, we can import it to any page (or other components) and use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import Header from "../components/Header.astro";
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Header&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro components are no different from pages. Any code defined between fences is executed on the server. JavaScript is stripped out before sending the content to the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layouts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.astro.build/en/core-concepts/layouts/"&gt;Layouts&lt;/a&gt; are used to refine reusable UI structures. They are technically components, so the syntax stays the same.&lt;/p&gt;

&lt;p&gt;Let's replace the content of &lt;code&gt;index.astro&lt;/code&gt; with a layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/pages/index.astro
import SiteLayout from "../layouts/SiteLayout.astro";
---

&lt;span class="nt"&gt;&amp;lt;SiteLayout&amp;gt;&amp;lt;/SiteLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, layouts are stored in the &lt;code&gt;src/layouts&lt;/code&gt; folder by convention.&lt;/p&gt;

&lt;p&gt;Layouts, like components, can include other components. Here we have extracted the structure in &lt;code&gt;index.astro&lt;/code&gt; and added a &lt;code&gt;Footer&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/layouts/SiteLayout.astro
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Header&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Footer&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Props and slots
&lt;/h3&gt;

&lt;p&gt;So far, our website has been completely static. In order to pass data&lt;br&gt;
between pages and components, we need to understand how &lt;strong&gt;props&lt;/strong&gt; and&lt;br&gt;
&lt;strong&gt;slots&lt;/strong&gt; work.&lt;/p&gt;

&lt;p&gt;Components and layouts can define and accept &lt;a href="https://docs.astro.build/en/core-concepts/astro-components/#component-props"&gt;props&lt;/a&gt;(short for properties) via the global &lt;code&gt;Astro.props&lt;/code&gt;. Values passed through props are accessible to the component before rendering.&lt;/p&gt;

&lt;p&gt;We can read props in our component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/components/Header.astro
const { title } = Astro.props;
---

&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; 
&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of &lt;code&gt;title&lt;/code&gt; can be supplied when the component is called, which in the following example happens through the &lt;code&gt;SiteLayout&lt;/code&gt; layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/layouts/SiteLayout.astro
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
---
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Header&lt;/span&gt; &lt;span class="na"&gt;title = &lt;/span&gt;&lt;span class="s"&gt;"Welcome my Astro Blog!"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Footer&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Note that you need spaces surrounding the equal sign, i.e. &lt;code&gt;title="Hello"&lt;/code&gt; is NOT correct. Instead, it should be: &lt;code&gt;title = "Hello"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.astro.build/en/core-concepts/astro-components/#slots"&gt;Slot&lt;/a&gt; elements create placeholders for content to be injected later. To see how it works, we can add a &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; element in &lt;code&gt;src/layouts/SiteLayout.astro&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/layouts/SiteLayout.astro
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Header&lt;/span&gt; &lt;span class="na"&gt;title = &lt;/span&gt;&lt;span class="s"&gt;"Welcome my Astro Blog!"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Footer&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the HTML inside &lt;code&gt;&amp;lt;SiteLayout&amp;gt;&lt;/code&gt; is injected into the point where the slot is located.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// src/pages/index.astro
import SiteLayout from "../layouts/SiteLayout.astro";
---

&lt;span class="nt"&gt;&amp;lt;SiteLayout&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This content is rendered in the slot&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/SiteLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesting components, layouts, props, and slots give us the flexibility to create reusable UI files across the website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Islands and hydration
&lt;/h3&gt;

&lt;p&gt;Up to this point, we have not shipped any JavaScript to the user; everything is prerendered and served as pure HTML+CSS. How do we send make Astro send JavaScript to the browser? For that, we need to understand the &lt;strong&gt;islands architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The island architecture pattern aims to reduce the amount of JavaScript needed browser-side. Less JavaScript means less data sent and less computational power required on the user's device. An island is an autonomous component that bundles HTML, CSS, and — optionally —&lt;br&gt;
JavaScript. In the islands pattern, a page is made up of several independent islands.&lt;/p&gt;


&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ysI5KIpe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/90bbafa34424d32948e8011f41621a70914e577f4250d8fe491978fc7e2023c9.jpg" alt="A web page showing the island's architecture in action. The header and footer are static HTML and a sidebar navigation component that uses React for interactivity. The actual content is server-side rendered HTML." width="800" height="338"&gt;The islands architecture allows combining static HTML, server-rendered content, and interactive client-rendered components on one page without conflicts.



&lt;p&gt;Each island is prerendered, so there is no interactivity right after the page loads. Once the initial page is ready, the islands are converted into interactive content in a process called hydration. Hydration is a technique that converts static content delivered via static hosting or server-side rendering into a dynamic page by attaching event handlers to the HTML elements.&lt;/p&gt;


&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gN8GkZdT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/33f12a759a113ee19f710dc71eae508eb47e75c03fb5e0c89d6804f367904f5a.jpg" alt="A time sequence of a hydrated page. On the right side, we have the Astro server, and on the right, the browser. The browser requests the page, and the server sends only the HTML. This is the 'first contentful paint'. A little while later, the browser requests the JavaScript, and the server sends it. Once the JavaScript is loaded and executed, the page is interactive. " width="738" height="568"&gt;How hydration works. The first contentful paint happens right after all the HTML has been sent to the browser, which typically happens very quickly. The browser then requests and executes all the client-side JavaScript needed to make the page fully interactive.


&lt;h3&gt;
  
  
  Using Islands
&lt;/h3&gt;

&lt;p&gt;An example showing how Astro implements islands is in order. Let's first add a UI integration to the project. The following command installs &lt;code&gt;@astrojs/preact&lt;/code&gt; and &lt;code&gt;preact&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx astro add preact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a simple button to test the integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/MyButton.jsx&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;purple&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clicked&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As usual, Astro will try to strip away any JavaScript. So nothing would happen if we instantiated the component with &lt;code&gt;&amp;lt;MyButton /&amp;gt;&lt;/code&gt;. We need to tell Astro that we want this component treated as an island and hydrated accordingly by adding the &lt;a href="https://docs.astro.build/en/reference/directives-reference/#client-directives"&gt;template directive &lt;code&gt;client:load&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import MyButton from "../components/MyButton.jsx";
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;MyButton&lt;/span&gt; &lt;span class="na"&gt;client:load&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clicking the button should print "Hello!" in the browser console.&lt;/p&gt;

&lt;p&gt;The client directive caused Astro to hydrate the component. There are five levels of hydration with different priorities: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;client:load&lt;/code&gt; hydrates the component as soon as possible. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client:idle&lt;/code&gt; hydrates the component when the page is done loading. Useful for low-priority components that don't need immediate interactivity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client:visible={string}&lt;/code&gt; hydrates the component as soon as it appears in the viewport. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client:media={string}&lt;/code&gt; takes a CSS query as an argument and loads the component as soon as it's fulfilled. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client:only&lt;/code&gt; skips HTML rendering entirely and renders the component in-browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying Astro application with CI/CD
&lt;/h2&gt;

&lt;p&gt;Being a Node application, setting up an Astro build with CI/CD is very straightforward. We only need a couple of jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5wbOKjIM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/1cb815792494b3fbde8a3b6bf40671c8f4502f3f656ab9e27aba48d59b4e1dec.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5wbOKjIM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/1cb815792494b3fbde8a3b6bf40671c8f4502f3f656ab9e27aba48d59b4e1dec.jpg" alt="A screenshot of a Semaphore CI pipeline with two blocks: Install dependencies and Build. Each block has one job." width="324" height="151"&gt;&lt;/a&gt;&lt;/p&gt;
A possible continuous integration pipeline for Astro.



&lt;p&gt;The first job runs &lt;a href="https://semaphoreci.com/blog/reproducible-node-builds-with-npm-ci"&gt;npm ci&lt;/a&gt; to populate &lt;code&gt;node_modules&lt;/code&gt;. We use &lt;a href="https://docs.semaphoreci.com/ci-cd-environment/sem-version-managing-language-versions-on-linux/"&gt;sem-version&lt;/a&gt; to select a current Node version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;checkout
sem-version node 19.4
npm ci
cache store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second job runs &lt;code&gt;npm run build&lt;/code&gt; and stores the built application (located in the &lt;code&gt;dist&lt;/code&gt; folder) as a &lt;a href="https://docs.semaphoreci.com/essentials/artifacts/"&gt;workflow&lt;br&gt;
artifact&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;checkout
sem-version node 19.4
cache restore
npm run build
artifact push workflow dist/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the site is built, we can configure &lt;a href="https://semaphoreci.com/cicd"&gt;continuous&lt;br&gt;
deployment&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deployment targets
&lt;/h3&gt;

&lt;p&gt;Depending on the nature of the web application, Astro can be deployed as a static site à la Hugo or Gatsby, in which case we only need something as simple as an S3 bucket or GitHub pages, or as a full-fledged server-side rendered (SSR) application, where we need JavaScript- or TypeScript-enabled endpoints.&lt;/p&gt;

&lt;p&gt;Astro has built-in support for various popular &lt;a href="https://docs.astro.build/en/guides/deploy/"&gt;deployment targets&lt;/a&gt; such as Netlify, Firebase, Vercel, and Deno. Some only support either SSR or static hosting, while others can do both.&lt;/p&gt;

&lt;p&gt;Once we have chosen our deployment method, we can add a continuous deployment &lt;a href="https://semaphoreci.com/blog/cicd-pipeline"&gt;pipeline&lt;/a&gt; to automatically deploy the website on every change.&lt;/p&gt;

&lt;p&gt;Here's an example deployment pipeline that targets a Netlify static site.&lt;/p&gt;


&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U-PMnE6f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/98fe3294f91fb96df5e17814184d95a09988c6a331f49c3c4319aa02fb4051c0.jpg" alt="A screenshot of Semaphore CI/CD pipelines. We have added to the initial pipeline shown before a continuous deployment pipeline with one job that deploys to Netlify." width="800" height="233"&gt;The complete CI/CD workflow with continuous deployment to Netlify.



&lt;p&gt;For reference, the job commands are as follows. This supposes that we have already obtained an API token and saved it as a &lt;a href="https://docs.semaphoreci.com/essentials/using-secrets/"&gt;secret&lt;/a&gt; on Semaphore with the env variables NETLIFY_TOKEN and NETLIFY_SITE.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;checkout
artifact pull workflow dist
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; netlify-cli
netlify deploy &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dist &lt;span class="nt"&gt;--prod&lt;/span&gt; &lt;span class="nt"&gt;--auth&lt;/span&gt; &lt;span class="nv"&gt;$NETLIFY_TOKEN&lt;/span&gt; &lt;span class="nt"&gt;--site&lt;/span&gt; &lt;span class="nv"&gt;$NETLIFY_SITE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The popularity of this project has been nothing short of astronomical: in the first five months since the Astro 1.0 release, the project has amassed over 25,000 stars on GitHub. The success of Astro is not accidental. The Astro team has made a web framework that offers great ergonomics for developers and fast-loading websites for users, even if they are on low-power devices or slow connections.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy building!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Ruby Adds Support for WebAssembly: What is WebAssembly and how it benefits Ruby devs?</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Fri, 10 Feb 2023 21:18:38 +0000</pubDate>
      <link>https://forem.com/semaphore/ruby-adds-support-for-webassembly-what-is-webassembly-and-how-it-benefits-ruby-devs-2825</link>
      <guid>https://forem.com/semaphore/ruby-adds-support-for-webassembly-what-is-webassembly-and-how-it-benefits-ruby-devs-2825</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@joshuafuller?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Joshua Fuller&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/p8w7krXVY1k?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ruby has joined the ranks of languages capable of targeting WebAssembly with its &lt;a href="https://www.ruby-lang.org/en/downloads/" rel="noopener noreferrer"&gt;latest 3.2 release&lt;/a&gt;. This seemingly minor update might be the biggest thing that has happened to the language since Rails, as it lets Ruby developers go beyond the backend. By porting their code to WebAssembly, they can run it anywhere: on the frontend, on embedded devices, as serverless functions, in place of containers, or on the edge. WebAssembly has the potential to make Ruby a universal language.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is WebAssembly?
&lt;/h2&gt;

&lt;p&gt;WebAssembly (commonly shortened as Wasm) is a binary low-level instruction format that runs on a virtual machine. The language was designed as an alternative to JavaScript. Its aim is to run applications on any browser at near-native speeds. Wasm can be targeted from any high-level language like C, Go, Rust, and now also Ruby.&lt;/p&gt;

&lt;p&gt;Wasm &lt;a href="https://www.w3.org/2019/12/pressrelease-wasm-rec.html.en" rel="noopener noreferrer"&gt;became a W3C standard&lt;/a&gt; in 2019, opening the path to writing high-performing applications for the Web. The standard itself is still evolving, and its ecosystem is growing. Currently, this technology is receiving a lot of focus from the Cloud Native Computing Foundation (CNCF), with &lt;a href="https://www.cncf.io/blog/2021/08/05/cloud-native-webassembly/" rel="noopener noreferrer"&gt;several projects under development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Wasm's design sits on two pillars: &lt;em&gt;portability&lt;/em&gt; and &lt;em&gt;security&lt;/em&gt;. The Wasm binary can run on any modern browser, even mobile devices. For security, Wasm programs run in a sandboxed, memory-safe VM. As such, they cannot access any system resources: they can’t change the filesystem or access the network or memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebAssembly brings portability to the next level
&lt;/h2&gt;

&lt;p&gt;Let’s say you want to build an application targeting many systems, e.g. Linux, Windows, and macOS. What are your options?&lt;/p&gt;

&lt;p&gt;You could use a compiled language like C and build a binary for each target.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpas099viuffl9j1u8pd0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpas099viuffl9j1u8pd0.jpg" alt="Code is compiled into three formats: ELF binary for Linux, PE binary for Windows, and Mach binary for macOS. We have one source code and three binaries." width="350" height="515"&gt;&lt;/a&gt;&lt;br&gt;
Compiler portability creates multiple executable files&lt;/p&gt;

&lt;p&gt;Or, if you can rely on having the appropriate runtime installed you could choose an interpreted language like JavaScript or one that compiles to bytecode like Java.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3s0mbd702smwzzsui46.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3s0mbd702smwzzsui46.jpg" alt="The code is compiled into one universal bytecode format, which is executed on the platform via a runtime environment. The diagram shows Java compilation and the JRE running on each platform." width="350" height="662"&gt;&lt;/a&gt;&lt;br&gt;
Code is compiled into an intermediate bytecode. This system relies on having a runtime environment, or VM, installed on the client.&lt;/p&gt;

&lt;p&gt;What if you have a container runtime in the client? In that case, you could build a Docker image for each platform type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t28hz6nuaho9y5xltoa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t28hz6nuaho9y5xltoa.jpg" alt="Diagram of a container workflow. The code is built with Docker. The process generates three types of images: one for Linux, one for Windows and one for ARM. On the client, the runtime pulls the correct image type and runs it." width="350" height="601"&gt;&lt;/a&gt;&lt;br&gt;
Code is compiled into platform-dependent images. A container runtime is required for clients, which pulls the correct image automatically.&lt;/p&gt;

&lt;p&gt;For Ruby developers historically, the only option was to distribute the code. That meant that users had to install the Ruby interpreter (or developers had to package the interpreter along with the application) to run the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv65d3qg6ghvdlpznqib.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv65d3qg6ghvdlpznqib.jpg" alt="The code is distributed directly. Clients must install the appropiate interpreter to execte the application." width="350" height="426"&gt;&lt;/a&gt;&lt;br&gt;
Code is shipped directly to users, who must have the interpreter installed in their systems to be able to run it.&lt;/p&gt;

&lt;p&gt;All these mechanisms provide portability, but at a cost: you must build, test, and distribute many images. Sometimes, you must also ship a suitable runtime with the release or tell the user to install it independently.&lt;/p&gt;

&lt;p&gt;WebAssembly (shortened as Wasm) takes portability to the next level: it allows you to build ONE binary and run it in &lt;a href="https://caniuse.com/wasm" rel="noopener noreferrer"&gt;any modern browser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6dnl14cxk78l4lj219a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6dnl14cxk78l4lj219a.jpg" alt="This shows the WebAssembly workflow. Code is compiled into a unique Wasm binary, which can run unmodified on any browser. We have a single binary that runs on Linux, macOS and Linux." width="350" height="598"&gt;&lt;/a&gt;&lt;br&gt;
WebAssembly compiles into a low-level assembly that every modern browser can execute. As a result, the same Wasm binary can run, unmodified, on every platform (even mobile).&lt;/p&gt;

&lt;p&gt;The ability to run code at native speed has allowed developers to build sites like Figma, and Google Earth or even &lt;a href="https://www.vimonlineeditor.com/" rel="noopener noreferrer"&gt;run Vim in the browser&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ruby adds support for WebAssembly
&lt;/h2&gt;

&lt;p&gt;The latest Ruby release ships with a Wasm port of the interpreter. Therefore, we can run Ruby code directly in the browser without the need for a backend.&lt;/p&gt;

&lt;p&gt;As you can see in the example below, all it takes to get started with the Ruby Wasm port is a couple of lines. The script downloads &lt;code&gt;ruby.wasm&lt;/code&gt; and instantiates the interpreter in the browser. After that, it takes the text of &lt;code&gt;text/ruby&lt;/code&gt; type and feeds it into the WebAssembly program.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/ruby"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;puts&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm that Ruby is running from the browser, i.e. not connecting with a backend, by opening the developers' tools. Here, you'll find once &lt;code&gt;ruby.wasm&lt;/code&gt; is downloaded, no further connections are needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3clzgoxusxall70mpmix.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3clzgoxusxall70mpmix.jpg" alt=" The browser console log is opened and shows: Hello, world!" width="703" height="160"&gt;&lt;/a&gt;&lt;br&gt;
Traditionally, JavaScript has been touted as the best language to learn because you have it everywhere. With WebAssembly, everyone can learn and experiment with Ruby using a browser. The output is printed in the developer's console.&lt;/p&gt;

&lt;p&gt;You can even see the contents of &lt;code&gt;ruby.wasm&lt;/code&gt; disassembled into text format in the “Sources” tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhadp0oy59p651eysyag5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhadp0oy59p651eysyag5.jpg" alt=" The browser’s developer tools are opened in the Sources tab. The list of sources includes a wasm folder with the Ruby interpreter downloaded. On the right pane, we see the contents of the disassembled binary in text form." width="800" height="339"&gt;&lt;/a&gt;&lt;br&gt;
We can see the downloaded Wasm file in the browsers web developer tools.&lt;/p&gt;

&lt;p&gt;You can check out the Wasm port online at the &lt;a href="https://try.ruby-lang.org/playground/" rel="noopener noreferrer"&gt;Ruby playground&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Working with the sandbox
&lt;/h2&gt;

&lt;p&gt;As said, Wasm programs run in a sandboxed VM that lacks access to the rest of the system. Therefore, Wasm applications &lt;em&gt;do not&lt;/em&gt; have access to the browser, filesystem, memory or the network. We'll need some JavaScript code to send and receive data from the sandbox.&lt;/p&gt;

&lt;p&gt;The following example shows how to read the output of a Ruby program and make changes to the page using the &lt;a href="https://www.npmjs.com/package/ruby-head-wasm-wasi" rel="noopener noreferrer"&gt;ruby-head-wasm-wasi&lt;/a&gt; NPM package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.umd.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DefaultRubyVM&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ruby-wasm-wasi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;DefaultRubyVM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printVersion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
        require "js"
        luckiness = ["Lucky", "Unlucky"].sample
        JS::eval("document.body.innerText = '#{luckiness}'")
      `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same package can also run Ruby code inside a Node project, allowing you to mix Ruby and JavaScript on the backend. You'll need to install the NPM package &lt;code&gt;ruby-head-wasm-wasi&lt;/code&gt; for the example to work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DefaultRubyVM&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ruby-head-wasm-wasi/dist/node.cjs.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="c1"&gt;// Tips: Replace the binary with debug info if you want symbolicated stack trace.&lt;/span&gt;
 &lt;span class="c1"&gt;// (only nightly release for now)&lt;/span&gt;
 &lt;span class="c1"&gt;// "./node_modules/ruby-head-wasm-wasi/dist/ruby.debug+stdlib.wasm"&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./node_modules/ruby-head-wasm-wasi/dist/ruby.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;DefaultRubyVM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
 luckiness = ["Lucky", "Unlucky"].sample
 puts "You are #{luckiness}"
 `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running ruby.wasm outside the browser
&lt;/h2&gt;

&lt;p&gt;While Wasm's primary design goal is running binary code in the browser, developers quickly realized the potential of a fast, safe, and universally portable binary format for software delivery. Wasm has the potential to become as big a Docker, greatly simplifying application deployment for embedded systems, &lt;a href="https://www.cncf.io/blog/2021/08/25/webassembly-serverless-functions-in-aws-lambda/" rel="noopener noreferrer"&gt;serverless functions&lt;/a&gt;, edge computing, or as a &lt;a href="https://nigelpoulton.com/what-is-runwasi/" rel="noopener noreferrer"&gt;replacement for containers on Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Running a Wasm application outside the browser requires an appropriate runtime that implements the WebAssembly VM and provides interfaces to the underlying system. There are a few competing solutions in this field, the most popular being &lt;a href="https://wasmtime.dev/" rel="noopener noreferrer"&gt;wasmtime&lt;/a&gt;, &lt;a href="https://wasmer.io/" rel="noopener noreferrer"&gt;wasmer&lt;/a&gt;, and &lt;a href="https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/README.md" rel="noopener noreferrer"&gt;WAMR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Ruby repository provides a &lt;a href="https://github.com/ruby/ruby.wasm#quick-example-how-to-package-your-ruby-application-as-a-wasi-application" rel="noopener noreferrer"&gt;complete example&lt;/a&gt; for bundling your application code into a custom Ruby image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Let’s remember that this is all cutting-edge tech. The whole Wasm ecosystem is moving fast. Right now, Ruby Wasm has a few limitations which significantly limit its usability in big projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No thread support.&lt;/li&gt;
&lt;li&gt;Spawning processes does not work.&lt;/li&gt;
&lt;li&gt;No network support.&lt;/li&gt;
&lt;li&gt;The garbage collector can create memory leaks.&lt;/li&gt;
&lt;li&gt;Gems and modules are unavailable unless you build a custom Wasm image.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The future is bright
&lt;/h2&gt;

&lt;p&gt;WebAssembly opens a world of exciting possibilities. It allows Ruby developers to escape the backend. As tooling around WebAssembly improves, Ruby will be able to reach new frontiers: the browser is no longer off-limits, and there will be new opportunities to run Ruby on the edge and as serverless applications.&lt;/p&gt;

&lt;p&gt;With the latest release, Ruby developers can begin experimenting with WebAssembly. It's the first step, for sure, and there is much more work to do before we see complex Ruby applications running in with this technology.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy assembling!&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>management</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Release Management: Is Your Product Ready for Success?</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Wed, 08 Feb 2023 19:24:42 +0000</pubDate>
      <link>https://forem.com/semaphore/release-management-is-your-product-ready-for-success-58o</link>
      <guid>https://forem.com/semaphore/release-management-is-your-product-ready-for-success-58o</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@javaistan?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Afif Ramdhasuma&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/jl4BQJs87Do?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Release Management: is your product ready for success?
&lt;/h1&gt;

&lt;p&gt;You never get a second chance to make a first impression. So how do you make sure it’s a good one? This matter is constantly in the mind of release managers and product owners, because they know that once a product is set loose into the world all bets are off.&lt;/p&gt;

&lt;p&gt;Although it may be tempting to release a product or new feature as soon as it is functional, users expect (and deserve) quality and polish. Before even reaching the beta test stage, a lot of non-technical work must be done: documentation must be written, security must be assessed, and business objectives must be established. These are some of the things we have learned about software release at Semaphore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project stages
&lt;/h2&gt;

&lt;p&gt;From idea to release, we can break a software project into roughly three stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design and development&lt;/strong&gt;: comprises everything from idea to MVP or working feature. We’ve discussed design patterns in other blog posts, so I won’t spend too much time on that at this point. All I will say is that there is no shortage of practices to help us uncover good designs, like writing &lt;a href="https://semaphoreci.com/blog/the-benefits-of-acceptance-testing"&gt;acceptance criteria&lt;/a&gt; and following &lt;a href="https://semaphoreci.com/blog/test-driven-development"&gt;Test-Driven Development&lt;/a&gt;, &lt;a href="https://semaphoreci.com/blog/2016/11/03/how-bdd-and-continuous-delivery-help-developers-maintain-flow.html"&gt;Behavior-Driven Development&lt;/a&gt;, or &lt;a href="https://semaphoreci.com/blog/domain-driven-design-microservices"&gt;Domain-Driven Development&lt;/a&gt; methodologies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech preview&lt;/strong&gt;: the sharp blade of the reality check. We bring a few users to get their early feedback so we can make improvements and reduce uncertainty at the release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release&lt;/strong&gt;: also known as &lt;em&gt;General Availability&lt;/em&gt; (GA). In this last stage, we make the release available to all users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stages are separated by &lt;em&gt;checkpoints&lt;/em&gt;. Of which we have two kinds &lt;em&gt;Definition of Ready&lt;/em&gt; and &lt;em&gt;Definition of Done&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition of Ready&lt;/strong&gt; (DoR): these are all required tasks before users can access the system or experience the feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Definition of Done&lt;/strong&gt; (DoD): consists of all the follow-up tasks closing a stage, like gathering user feedback, completing outstanding tickets, or doing a retrospective.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We cannot enter a new project stage or consider it done until all the tasks in DoR and DoD are ticked off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rDWfaiVE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/007cb6d285686ba9d435bc90c2cdaf67c11ae5b3988ff241d268d659c50e51f7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rDWfaiVE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/007cb6d285686ba9d435bc90c2cdaf67c11ae5b3988ff241d268d659c50e51f7.jpg" alt="A diagram showing the flow of release management. The first step is getting and idea. From there, it flows to design and development, an iterative process that produces a prototype or MVP. The next step is the technical preview. Before starting this step there is a checkpoint called Definition of Ready. Next we do the technical preview where we test with real users and get their feedback. This leads to more development. Before the preview is done we have to complete the Definition of Done checklist. The same process repeats for the general release. Namely, there is a Definition of Ready before the release and a Definition of Done after it. Only then, we can call the project done." width="800" height="325"&gt;&lt;/a&gt;&lt;br&gt;
The ready checkpoint (DoR) dictates if users can be allowed into our system. Thus, the technical preview can begin only once we meet the DoR. It does not end until we’ve completed all the tasks in the DoD. The same thing happens during GA release.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical preview
&lt;/h2&gt;

&lt;p&gt;The technical preview is an opportunity to get the user's feedback before the final release. We must bring them into the mix before evaluating whether a feature or product works. During the preview stage, we deploy the latest release in production and use &lt;a href="https://semaphoreci.com/blog/feature-flags"&gt;feature flags&lt;/a&gt; to control who can see the previewed feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zm0aMtUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/29e1ffdf889ae5dd159a675c212f07c1e58c19bb1406b833916b32635135f465.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zm0aMtUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/29e1ffdf889ae5dd159a675c212f07c1e58c19bb1406b833916b32635135f465.jpg" alt="Two groups of users access the production environment. The beta users have a feature flag enabled that allows them to see the previewed feature. General users do not have the feature flag and thus nothing changes for them." width="664" height="576"&gt;&lt;/a&gt;&lt;br&gt;
We can deploy the latest release in production and use feature flags to select which users access the previewed feature.&lt;/p&gt;

&lt;p&gt;Most users understand that they may find some kinks and will be happy to help us sort them out. Therefore, we can start the tech preview even if the application is not perfect (hint: it never will be).&lt;/p&gt;

&lt;p&gt;That being said, there are some precautions we need to take before any user is let into our systems. Here's where the DoR for the technical release comes into focus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Definition of Ready for the technical preview
&lt;/h3&gt;

&lt;p&gt;Let’s say we’re shipping a new feature in our product. Early feedback from real users is essential for its success. We can reach out to users who may benefit from the feature or be interested in testing it out.&lt;/p&gt;

&lt;p&gt;How do we know when we’re ready to invite users to check it out? We define a list of tasks and conditions that must happen before we can define our system as ready. These are tasks that are required to be complete for every feature before it can be shown to the users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance metrics&lt;/strong&gt;: every new component shipped should be tracked and added to the monitoring dashboard to observe its performance and stability. Semaphore, for instance, monitors the &lt;a href="https://sre.google/sre-book/monitoring-distributed-systems/#xref_monitoring_golden-signals"&gt;four golden signals&lt;/a&gt; (latency, traffic, errors, and saturation).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilization metrics&lt;/strong&gt;: we need to make sure &lt;em&gt;if&lt;/em&gt; the new features are being used and &lt;em&gt;how&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Logs&lt;/strong&gt;: shipping anything to production that's not covered in audit logs is a security issue. Every event should be logged to have full audit compliance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Assessment&lt;/strong&gt;: we must ensure the new components don’t have any critical vulnerabilities. This is important for two reasons. First, to protect users from security exploits. And second, to comply with any security standards the company follows, such as &lt;a href="https://semaphoreci.com/security"&gt;ISO 27001&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment to production&lt;/strong&gt;: the release should be deployed to production. This can be the primary productive environment or a staging copy that's as close as possible to the real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preliminary documentation&lt;/strong&gt;: the users will need instructions about what changed and how to use it. It doesn’t need to be full-blown documentation yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The list can be expanded to include more items as needed.&lt;/p&gt;

&lt;p&gt;Once we have our DoR defined and fulfilled, we’re ready to start the technical preview. The feature passes to the “in-review” state, and we can begin the feedback-gathering process that may last weeks or months.&lt;/p&gt;

&lt;p&gt;💡 Instead of technical previews, the engineering team at Semaphore used to have two kinds of betas: public and private, which had different processes and requirements. Having two sets of similar-but-not-quite-the-same DoRs and DoDs ended up being unnecessarily complicated. So, the processes were streamlined into one. Users are invited to the technical preview based on needs and interest in a given feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Definition of Done for the technical preview
&lt;/h3&gt;

&lt;p&gt;The technical preview ends when we've gathered enough feedback and refined the system to the point we consider it ready for release.&lt;/p&gt;

&lt;p&gt;Before we can close the technical preview, however, there are some tasks to complete, which will depend on the DoD for our project.&lt;/p&gt;

&lt;p&gt;The items in the DoD bucket vary, as they will change from team to team or from company to company. For Semaphore, the list includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gathering feedback&lt;/strong&gt;: the whole point of this exercise is to gather actionable feedback from the users. Feedback lets us better incorporate the voice of the customer into the system. It lets us find bugs and improve usability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaning up any remaining tasks&lt;/strong&gt;: address and close outstanding tickets. Ensure all the problems and feedback obtained during the technical preview have either been fixed or have mitigation plans in motion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communicating internally&lt;/strong&gt;: everyone involved in supporting production should be notified about any problems found during the technical preview (and their solutions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting up alerts&lt;/strong&gt;: we need to be aware of a problem in production as soon as it happens. This includes configuring Slack/Teams alerts, setting up pagers, and ensuring that the on-call personnel are on the same page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  General availability release
&lt;/h2&gt;

&lt;p&gt;The moment of truth we’ve prepared for so hard is close at hand. Users will not be as forgiving during a general availability (GA) release as during a tech preview. Problems might disrupt their work. That’s why we went to such lengths to minimize the chance of problems in the release. However, no amount of preparation will guarantee a successful release. So, we need to prepare for any issues that might arise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Definition of Ready for release
&lt;/h3&gt;

&lt;p&gt;The software might be ready to release, but that doesn't mean &lt;em&gt;we&lt;/em&gt; are ready for the release. So, we must define and fulfill the GA release DoR checklist before going into the next step.&lt;/p&gt;

&lt;p&gt;As always, every project has different needs. As a guideline, here’re are some of the tasks that are considered necessary at Semaphore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Writing the public docs&lt;/strong&gt;: public docs must be updated with the details of the new feature. In addition, it should be clear which plans have support for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skilling up internally&lt;/strong&gt;: everyone in the company, especially support and customer-facing staff, should be briefed on the upcoming changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting KPIs&lt;/strong&gt;: based on technical preview utilization, we can set business objectives and key performance indicators (KPI) for the feature in question. This may include new revenue expected, utilization metrics, or feature adoption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing a change log&lt;/strong&gt;: the change log should include everything that has changed since the last release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting up alerts&lt;/strong&gt;: we need to be aware of any problem with the new features shipped as soon as it happens. This includes Slack/Teams alerts and pager notifications for the on-call personnel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planning a marketing strategy&lt;/strong&gt;: the marketing strategy communicates the changes and generates user interest. A good campaign can bring new customers or convert free users into paid ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Definition of Done for GA
&lt;/h3&gt;

&lt;p&gt;With some luck, the release went smoothly, and users are happy with the new features. What now? Can we move to the next project? Not yet. This project is not done until all items in the DoD are checked off.&lt;/p&gt;

&lt;p&gt;What's typically left at this point is doing some cleanup work and tidying up loose ends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean leftover tickets&lt;/strong&gt;: after shipping the new features to GA, there might be some remaining low-priority tasks. They should be addressed or scheduled to be worked on later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI and API Support&lt;/strong&gt;: provide support to the new feature via public APIs or the CLI tools, because this makes it easier for users to access resources from the terminal and integrate them into their custom tooling. If this support is integral to the new feature, we can move this item to the DoR checklist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do the project retrospective&lt;/strong&gt;: The retrospective is one of the most critical phases of a project. It’s a space for the team to discuss what went well and what can be improved for the next project. Also, it allows the team to identify potential pitfalls early and resolve conflict areas.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It's a mistake to think that the hard part of software development is coding. In reality, the real challenge comes when you open the floodgates and users pour in. Unless everything is prepared, issues can compound and overtake any engineering team. Having a formal definition of what constitutes being ready and done serves us to avoid rushing through a release.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy releasing!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>programming</category>
      <category>startup</category>
    </item>
    <item>
      <title>Measuring Page Speed with Lighthouse</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Tue, 10 Jan 2023 21:49:31 +0000</pubDate>
      <link>https://forem.com/semaphore/measuring-page-speed-with-lighthouse-1m16</link>
      <guid>https://forem.com/semaphore/measuring-page-speed-with-lighthouse-1m16</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@paulius005?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Paulius Dragunas&lt;/a&gt; on &lt;a href="https://unsplash.com/images/things/lighthouse?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Page speed matters more than you think. According to &lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-new-industry-benchmarks/"&gt;research by Google&lt;/a&gt;, the probability of users staying on your site plummets as loading speed slows down. A site that loads in 10 seconds increases the bounce rate by a whopping 123%. In other words, speed equals revenue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ddP2edoZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/12ea625382d312655c16e7323d08d14af46381763659380b9b5a34983b2ca921.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ddP2edoZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/12ea625382d312655c16e7323d08d14af46381763659380b9b5a34983b2ca921.jpg" alt="Graph showing bounce rates for page load times. The values shown are 1 to 3 seconds = 32% bounce rate, 1 to 5 seconds = 90%, 1 to 6 seconds 106%, and 1 to 10 seconds 123%." width="800" height="558"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-new-industry-benchmarks/"&gt;find out how you stack up to new industry benchmarks for mobile page speed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How can we ensure that our pages are loading at top speed? The answer is to measure them regularly with Lighthouse and CI/CD.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring page speed with Lighthouse
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/overview/"&gt;Lighthouse&lt;/a&gt; is a page speed benchmark tool created by Google. It runs a battery of tests against your website and produces a report with detailed advice to improve performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7M1sJq82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/cf359bafc22b38ec1f37d80df83c814be675fc59fe14a9c5d10db735add800bf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7M1sJq82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/cf359bafc22b38ec1f37d80df83c814be675fc59fe14a9c5d10db735add800bf.jpg" alt="The Lighthouse HTML report. Includes scores for Performance, Accessibility, PWA, and Best practices. It also shows a sequence of how the webpage is rendered over time." width="558" height="566"&gt;&lt;/a&gt;&lt;br&gt;
Lighthouse running inside Google Chrome.&lt;/p&gt;

&lt;p&gt;⚠️ You might be surprised at the low scores Lighthouse presents. This is because the tool simulates mid-tier mobile devices on a 4G connection.&lt;/p&gt;

&lt;p&gt;While Lighthouse's primary focus is performance, it can assess other things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: suggests opportunities to make the content more accessible. This covers ensuring that the page is optimized for screen readers, all elements have labels, or the site is browsable with the keyboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best practices&lt;/strong&gt;: checks for various sane practices that improve speed and security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt;: performs various checks to ensure that the page is SEO-optimized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PWA&lt;/strong&gt;: ensures the page passes progressive web application tests, which improves user experience on mobile devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4 ways of running Lighthouse
&lt;/h3&gt;

&lt;p&gt;Lighthouse is an open-source project that you can run in different ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Since it is included in Google Chrome, you can run it directly from the browser. Click on &lt;strong&gt;More tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Developer Tools&lt;/strong&gt; and open the Lighthouse tab.&lt;/li&gt;
&lt;li&gt;If you have Node installed, you can run &lt;code&gt;npm install -g lighthouse&lt;/code&gt; and run the tool in the command line like this: &lt;code&gt;lighthouse https://semaphoreci.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You can include it in your code as a Node package.&lt;/li&gt;
&lt;li&gt;And finally, Lighthouse has a &lt;a href="https://github.com/GoogleChrome/lighthouse-ci"&gt;CI version&lt;/a&gt; you can run in your &lt;a href="https://semaphoreci.com/continuous-integration"&gt;continuous integration&lt;/a&gt;. We’ll use this method to schedule periodical benchmarks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to learn how to run Lighthouse with CI/CD to get a report on every change, check out this tutorial: &lt;a href="https://semaphoreci.com/blog/lighthouse-page-speed#setting-up-lighthouse-ci"&gt;Setting up Lighthouse CI&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up a dashboard
&lt;/h3&gt;

&lt;p&gt;The Lighthouse CI project includes an &lt;a href="https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md"&gt;optional dashboard&lt;/a&gt; that lets you browse historical data and find trends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QDF0Kumh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/eb78ab83e91917628693408da1542901efc75dbdc10ee7c3e8e37ae20f093dac.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QDF0Kumh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/tomfern/blog-assets/master/images/eb78ab83e91917628693408da1542901efc75dbdc10ee7c3e8e37ae20f093dac.jpg" alt="The Lighthouse Server Dashboard. It shows a graph of the page score over time and entries for the last 5 runs." width="800" height="580"&gt;&lt;/a&gt;&lt;br&gt;
Lighthouse CI dashboard.&lt;/p&gt;

&lt;p&gt;Installing the dashboard requires a separate server and database. You’ll need a dedicated machine and persistent storage to save historical data.&lt;/p&gt;

&lt;p&gt;The downside of this approach is obvious — you need to manage yet another server. But it may be worth doing if you have a lot of sites to analyze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time is money
&lt;/h2&gt;

&lt;p&gt;Users are drawn to fast and responsive websites. The problem is that measuring page speed reliably is challenging since you cannot assume that everyone is on a fast connection and uses a top-tier device. With Lighthouse in your CI/CD pipeline, you can get results closer to real-life conditions and insights to help you continually improve.&lt;/p&gt;

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

</description>
      <category>performance</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Taming Cloud Costs with Infracost</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Mon, 09 Jan 2023 18:25:43 +0000</pubDate>
      <link>https://forem.com/semaphore/taming-cloud-costs-with-infracost-2c88</link>
      <guid>https://forem.com/semaphore/taming-cloud-costs-with-infracost-2c88</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@mathieustern?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Mathieu Stern&lt;/a&gt; on &lt;a href="https://unsplash.com/images/things/money?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we combine the cloud with IaC tools like Terraform and &lt;a href="https://semaphoreci.com/cicd" rel="noopener noreferrer"&gt;continuous deployment&lt;/a&gt; we get the almost magical ability to create resources on demand. For all its benefits, however, the cloud has also introduced a set of difficulties, one of which is estimating cloud costs accurately.&lt;/p&gt;

&lt;p&gt;Cloud providers have &lt;a href="https://www.infracost.io/blog/why-are-cloud-costs-so-complex/" rel="noopener noreferrer"&gt;complex cost structures&lt;/a&gt; that are constantly changing. AWS, for example, offers &lt;a href="https://aws.amazon.com/ec2/pricing/on-demand/" rel="noopener noreferrer"&gt;536 types of EC2 Linux machines&lt;/a&gt;. Many of them have similar names and features. Take for example "m6g.2xlarge" and "m6gd.2xlarge" — the only difference is that the second comes with an SSD drive, which will add $60 dollars to the bill. Often, making a mistake in defining your infrastructure can cause your bill to balloon at the end of the month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4rwm96cqz3j4wzxlbbe.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4rwm96cqz3j4wzxlbbe.jpeg" alt="Man in front of a blackboaard filled with equations. The text says: Calculating AWS Resources Cost" width="719" height="483"&gt;&lt;/a&gt;&lt;br&gt;
It’s so easy to go above budget.&lt;/p&gt;

&lt;p&gt;We can set up billing alerts, but there are no guarantees that they will work. Alerts can happen during the weekend or be delayed, making us shoot past our budget in a few hours.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1542449611440328704-250" src="https://platform.twitter.com/embed/Tweet.html?id=1542449611440328704"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1542449611440328704-250');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1542449611440328704&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;So, how can we avoid this problem and use the cloud with confidence?&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Infracost
&lt;/h2&gt;

&lt;p&gt;Infracost is an &lt;a href="https://github.com/infracost/infracost" rel="noopener noreferrer"&gt;open-source project&lt;/a&gt; that helps us understand how and where we’re spending our money. It gives a detailed breakdown of actual infrastructure costs and calculates how changes impact them. Basically, Infracost is a &lt;code&gt;git diff&lt;/code&gt; for billing.&lt;/p&gt;

&lt;p&gt;Infracost has two versions: a VSCode addon and a command line program. Both do the same thing: parse Terraform code, pull the current cost price points from a &lt;a href="https://github.com/infracost/cloud-pricing-api" rel="noopener noreferrer"&gt;cloud pricing API&lt;/a&gt;, and output an estimate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcxq3ocnphmnvj1lqvnm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcxq3ocnphmnvj1lqvnm.jpg" alt="How Infracost works. It reads code from the repository and pulls the appropriate service costs from an API service. The IDE version prints the estimates on the screen while editing. The CLI version prints the result in the terminal, posts comments on GitHub, Bitbucket, or Gitlab, and can stop a CI/CD deployment if limits are exceeded." width="800" height="545"&gt;&lt;/a&gt;&lt;br&gt;
You can use Infracost pricing API for free or host your own. The paid tier includes a cloud dashboard to track changes over time.&lt;/p&gt;

&lt;p&gt;We can see the estimates right in the IDE:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cd3crkklq6dm3u2q443.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cd3crkklq6dm3u2q443.gif" alt="A GIF showing how Infracost shows cost estimates in real-time as a developer changes a Terraform file." width="719" height="539"&gt;&lt;/a&gt;&lt;br&gt;
Real-time cost estimation on VSCode.&lt;/p&gt;

&lt;p&gt;Or as comments in pull requests or commits:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ubt2clatczehhan1nsj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ubt2clatczehhan1nsj.jpg" alt="A GitHub Pull Request conversation showing an automated message from Infracost with the cost estimate." width="800" height="573"&gt;&lt;/a&gt;&lt;br&gt;
Cost change information in the PR.&lt;/p&gt;

&lt;p&gt;Infracost also has an optional &lt;a href="https://www.infracost.io/pricing/" rel="noopener noreferrer"&gt;Infracost Cloud&lt;/a&gt;, which comes with a paid tier and includes features like Jira integration, custom price books, and a dashboard to keep track of costs over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf55ur5445qxkzzujevs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf55ur5445qxkzzujevs.jpg" alt="The Infracost paid dashboard showing how costs change over time." width="780" height="353"&gt;&lt;/a&gt;&lt;br&gt;
The paid tier includes a dashboard to track spending over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Infracost
&lt;/h2&gt;

&lt;p&gt;To try out Infracost, we’ll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Infracost API key. You can get one by signing up for free at &lt;a href="https://www.infracost.io/" rel="noopener noreferrer"&gt;Infracost.io&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://www.infracost.io/docs/" rel="noopener noreferrer"&gt;Infracost CLI&lt;/a&gt; installed in your machine.&lt;/li&gt;
&lt;li&gt;Some Terraform files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the CLI tool is installed, run &lt;code&gt;infracost auth login&lt;/code&gt; to retrieve the API key. Now we’re ready to go.&lt;/p&gt;

&lt;p&gt;The first command we’ll try is &lt;code&gt;infracost breakdown&lt;/code&gt;. It analyzes Terraform plans and prints out a cost estimate. The &lt;code&gt;--path&lt;/code&gt; variable must point to the folder containing your Terraform files. For example, imagine we want to provision an "a1.medium" EC2 instance with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
 &lt;span class="nx"&gt;skip_credentials_validation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
 &lt;span class="nx"&gt;skip_requesting_account_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"myserver"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;ami&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-674cbc1e"&lt;/span&gt;
 &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"a1.medium"&lt;/span&gt;

 &lt;span class="nx"&gt;root_block_device&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;volume_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At current rates, this instance costs $28.62 per month to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;infracost breakdown &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

 Name Monthly Qty Unit Monthly Cost

 aws_instance.myserver
 ├─ Instance usage &lt;span class="o"&gt;(&lt;/span&gt;Linux/UNIX, on-demand, a1.medium&lt;span class="o"&gt;)&lt;/span&gt; 730 hours &lt;span class="nv"&gt;$18&lt;/span&gt;.62
 └─ root_block_device
 └─ Storage &lt;span class="o"&gt;(&lt;/span&gt;general purpose SSD, gp2&lt;span class="o"&gt;)&lt;/span&gt; 100 GB &lt;span class="nv"&gt;$10&lt;/span&gt;.00

 OVERALL TOTAL &lt;span class="nv"&gt;$28&lt;/span&gt;.62
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we add some extra storage (600GB of EBS), the cost increases to $155.52, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;infracost breakdown &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

 Name Monthly Qty Unit Monthly Cost

 aws_instance.myserver
 ├─ Instance usage &lt;span class="o"&gt;(&lt;/span&gt;Linux/UNIX, on-demand, a1.medium&lt;span class="o"&gt;)&lt;/span&gt; 730 hours &lt;span class="nv"&gt;$18&lt;/span&gt;.62
 ├─ root_block_device
 │ └─ Storage &lt;span class="o"&gt;(&lt;/span&gt;general purpose SSD, gp2&lt;span class="o"&gt;)&lt;/span&gt; 100 GB &lt;span class="nv"&gt;$10&lt;/span&gt;.00
 └─ ebs_block_device[0]
 ├─ Storage &lt;span class="o"&gt;(&lt;/span&gt;provisioned IOPS SSD, io1&lt;span class="o"&gt;)&lt;/span&gt; 600 GB &lt;span class="nv"&gt;$75&lt;/span&gt;.00
 └─ Provisioned IOPS 800 IOPS &lt;span class="nv"&gt;$52&lt;/span&gt;.00

 OVERALL TOTAL &lt;span class="nv"&gt;$155&lt;/span&gt;.62
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Infracost can also calculate usage-based resources like AWS Lambda. Let's see what happens when we swap the EC2 instance for serverless functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
 &lt;span class="nx"&gt;skip_credentials_validation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
 &lt;span class="nx"&gt;skip_requesting_account_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"my_lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my_lambda"&lt;/span&gt;
 &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:lambda:us-east-1:account-id:resource-id"&lt;/span&gt;
 &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"exports.test"&lt;/span&gt;
 &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nodejs12.x"&lt;/span&gt;
 &lt;span class="nx"&gt;memory_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;infracost breakdown&lt;/code&gt; yields a total cost of 0 dollars:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;infracost breakdown &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

 Name Monthly Qty Unit Monthly Cost

 aws_lambda_function.my_lambda
 ├─ Requests Monthly cost depends on usage: &lt;span class="nv"&gt;$0&lt;/span&gt;.20 per 1M requests
 └─ Duration Monthly cost depends on usage: &lt;span class="nv"&gt;$0&lt;/span&gt;.0000166667 per GB-seconds

 OVERALL TOTAL &lt;span class="nv"&gt;$0&lt;/span&gt;.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can’t be right unless no one uses our Lambda function, which is precisely what the tool assumes by default. We can fix this by providing an estimate via a usage file.&lt;/p&gt;

&lt;p&gt;We can create a sample usage file with this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ infracost breakdown --sync-usage-file --usage-file usage.yml --path .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can now provide estimates by editing &lt;code&gt;usage.yml&lt;/code&gt;. The following example consists of 5 million requests with an average runtime of 300 ms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;resource_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;aws_lambda_function.my_lambda&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;monthly_requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5000000&lt;/span&gt; 
 &lt;span class="na"&gt;request_duration_ms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;300&lt;/span&gt; 

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

&lt;/div&gt;



&lt;p&gt;We’ll tell infracost to use the usage file with &lt;code&gt;--usage-file&lt;/code&gt; to get a proper cost estimate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;infracost breakdown &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--usage-file&lt;/span&gt; usage.yml

 Name Monthly Qty Unit Monthly Cost

 aws_lambda_function.my_lambda
 ├─ Requests 5 1M requests &lt;span class="nv"&gt;$1&lt;/span&gt;.00
 └─ Duration 1,500,000 GB-seconds &lt;span class="nv"&gt;$25&lt;/span&gt;.00

 OVERALL TOTAL &lt;span class="nv"&gt;$26&lt;/span&gt;.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s much better. Of course, this is accurate as long as our usage file is correct. If you’re unsure, you can &lt;a href="https://www.infracost.io/docs/features/usage_based_resources/#fetch-from-cloud-apis" rel="noopener noreferrer"&gt;integrate Infracost with the cloud provider&lt;/a&gt; and pull the utilization metrics from the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git diff for cost changes
&lt;/h2&gt;

&lt;p&gt;Infracost can save results in JSON by providing the &lt;code&gt;--format json&lt;/code&gt; and &lt;code&gt;--out-file&lt;/code&gt; options. This gives us a file we can check in source control and use as a baseline.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ infracost breakdown --path . --format json --usage-file usage.yml --out-file baseline.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can now compare changes by running &lt;code&gt;infracost diff&lt;/code&gt;. Let’s see what happens if the Lambda execution time goes from 300 to 350 ms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;infracost diff &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--compare-to&lt;/span&gt; baseline.json &lt;span class="nt"&gt;--usage-file&lt;/span&gt; usage.yml

~ aws_lambda_function.my_lambda
 +&lt;span class="nv"&gt;$4&lt;/span&gt;.17 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$26&lt;/span&gt;.00 → &lt;span class="nv"&gt;$30&lt;/span&gt;.17&lt;span class="o"&gt;)&lt;/span&gt;

 ~ Duration
 +&lt;span class="nv"&gt;$4&lt;/span&gt;.17 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$25&lt;/span&gt;.00 → &lt;span class="nv"&gt;$29&lt;/span&gt;.17&lt;span class="o"&gt;)&lt;/span&gt;

Monthly cost change &lt;span class="k"&gt;for &lt;/span&gt;TomFern/infracost-demo/dev
Amount: +&lt;span class="nv"&gt;$4&lt;/span&gt;.17 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$26&lt;/span&gt;.00 → &lt;span class="nv"&gt;$30&lt;/span&gt;.17&lt;span class="o"&gt;)&lt;/span&gt;
Percent: +16%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the impact is a 16% increase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Infracost with CI/CD
&lt;/h2&gt;

&lt;p&gt;We’ve seen how this tool can help us estimate cloud costs. That’s valuable information, but what role does Infracost take in &lt;a href="https://semaphoreci.com/continuous-integration" rel="noopener noreferrer"&gt;continuous integration&lt;/a&gt;? To answer that, we must understand what &lt;code&gt;infracost comment&lt;/code&gt; does.&lt;/p&gt;

&lt;p&gt;The comment command takes a JSON file generated by &lt;code&gt;infracost diff&lt;/code&gt; and posts its contents directly into GitHub, Bitbucket, or Gitlab. Thus, by running Infracost inside CI, we make relevant cost information available to everyone on the team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskt6b3xi8wd5u4g2f5ct.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskt6b3xi8wd5u4g2f5ct.jpg" alt="An automated comment on GitHub with cost differences caused by the commit." width="800" height="270"&gt;&lt;/a&gt;&lt;br&gt;
Infracost comment on the cost difference in a GitHub commit.&lt;/p&gt;

&lt;p&gt;If you want to learn how to setup CI/CD with Infracost, check out this tutorial: &lt;a href="https://semaphoreci.com/blog/infracost#running-infracost-on-ci-cd" rel="noopener noreferrer"&gt;Running Infracost on CI/CD&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Working with monorepos
&lt;/h3&gt;

&lt;p&gt;You will likely have separate Terraform files for each subproject if you work with a &lt;a href="https://semaphoreci.com/blog/what-is-monorepo" rel="noopener noreferrer"&gt;monorepo&lt;/a&gt;. In this case, you should add an &lt;a href="https://www.infracost.io/docs/features/config_file/" rel="noopener noreferrer"&gt;infracost config file&lt;/a&gt; at the project's root. This allows you to specify the project names and where Terraform and usage files are located. You can also set environment variables and other options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;

&lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
 &lt;span class="na"&gt;usage_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev/infracost-usage.yml&lt;/span&gt;
 &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;

 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
 &lt;span class="na"&gt;usage_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod/infracost-usage.yml&lt;/span&gt;
 &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${PROD_AWS_ACCESS_KEY_ID}&lt;/span&gt;
 &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${PROD_AWS_SECRET_ACCESS_KEY}&lt;/span&gt;
 &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the config file is involved, you must replace the &lt;code&gt;--path&lt;/code&gt; argument with &lt;code&gt;--config-file&lt;/code&gt; in all your commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing policies
&lt;/h3&gt;

&lt;p&gt;One more trick Infracost has up its sleeve is &lt;a href="https://www.infracost.io/blog/cloud-cost-policies/" rel="noopener noreferrer"&gt;enforcing policies&lt;/a&gt;. Policies are rules that evaluate the output of &lt;code&gt;infracost diff&lt;/code&gt; and stop the CI pipeline if a resource goes over budget. This feature allows managers and team leads to enforce limits. When the policy fails, the CI/CD pipeline stops with an error, preventing the infrastructure from being provisioned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foym7r3u568roewaszxao.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foym7r3u568roewaszxao.jpg" alt="A pull request on GitHub with a warning that a policy has been broken." width="800" height="324"&gt;&lt;/a&gt;&lt;br&gt;
When a policy is in place, Infracost warns us if any limits are exceeded.&lt;/p&gt;

&lt;p&gt;Infracost implements policies using &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/" rel="noopener noreferrer"&gt;Open Policy Agent&lt;/a&gt; (OPA), which uses the Rego language to encode policy rules.&lt;/p&gt;

&lt;p&gt;Rego has a ton of features, and it’s &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/#the-basics" rel="noopener noreferrer"&gt;worth digging in to learn it thoroughly&lt;/a&gt;, but for our purposes, we only need to learn a few keywords:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;deny[out]&lt;/code&gt; defines a new policy rule that fails if the &lt;code&gt;out&lt;/code&gt; object has &lt;code&gt;failed: true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;msg&lt;/code&gt;: defines the error message shown when the policy fails.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;out&lt;/code&gt;: defines the logic that makes the policy pass or fails.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt;: references the contents of the JSON object generated with &lt;code&gt;infracost diff&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following example shows a policy that fails when the total budget exceeds $1,000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="c1"&gt;# policy.rego&lt;/span&gt;

&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;infracost&lt;/span&gt;

&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

 &lt;span class="c1"&gt;# define a variable&lt;/span&gt;
 &lt;span class="n"&gt;maxMonthlyCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;

 &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="s2"&gt;"Total monthly cost must be less than $%.2f (actual diff is $%.2f)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;maxMonthlyCost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;totalMonthlyCost&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"msg"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;to_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;totalMonthlyCost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxMonthlyCost&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another example that fails if the cost difference is equal to or greater than $500.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;infracost&lt;/span&gt;

&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

 &lt;span class="c1"&gt;# maxDiff defines the threshold that you require the cost estimate to be below&lt;/span&gt;
 &lt;span class="n"&gt;maxDiff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;500.0&lt;/span&gt;

 &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="s2"&gt;"Total monthly cost diff must be less than $%.2f (actual diff is $%.2f)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;maxDiff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diffTotalMonthlyCost&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"msg"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;to_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diffTotalMonthlyCost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxDiff&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can experiment and try several examples online on the &lt;a href="https://play.openpolicyagent.org/p/o1MLyC74CJ" rel="noopener noreferrer"&gt;OPA playground&lt;/a&gt;.&lt;br&gt;
To enforce a policy, you must add the &lt;code&gt;--policy-path&lt;/code&gt; option in any of the &lt;code&gt;infracost comment&lt;/code&gt; commands like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
checkout
infracost diff &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--usage-file&lt;/span&gt; usage.yml &lt;span class="nt"&gt;--compare-to&lt;/span&gt; baseline.json &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--out-file&lt;/span&gt; /tmp/infracost-diff-commit.json
infracost comment github &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/infracost-diff-commit.json &lt;span class="nt"&gt;--repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$SEMAPHORE_GIT_REPO_SLUG&lt;/span&gt; &lt;span class="nt"&gt;--commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$SEMAPHORE_GIT_SHA&lt;/span&gt; &lt;span class="nt"&gt;--github-token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_API_KEY&lt;/span&gt; &lt;span class="nt"&gt;--policy-path&lt;/span&gt; policy.rego &lt;span class="nt"&gt;--behavior&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The power to spin up resources instantly is a double-edged knife: a typo in a Terraform file can be a costly mistake. Staying proactive when managing our cloud infrastructure is essential to sticking to the budget and avoiding nasty surprises at the end of the month. If you’re already automating deployment with &lt;a href="https://semaphoreci.com/cicd" rel="noopener noreferrer"&gt;continuous deployment&lt;/a&gt; and managing services with Terraform, you may as well add Infracost to the mix to make more informed decisions and impose spending limits. Setting this up takes only a few minutes and can save thousands of dollars down the road.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>music</category>
      <category>technology</category>
    </item>
  </channel>
</rss>
