<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Semaphore</title>
    <description>The latest articles on DEV Community by Semaphore (semaphore).</description>
    <link>https://dev.to/semaphore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1183%2Fc8d02a31-85ba-4528-b043-30ee19b6db2f.png</url>
      <title>DEV Community: Semaphore</title>
      <link>https://dev.to/semaphore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/semaphore"/>
    <language>en</language>
    <item>
      <title>Best GitHub Actions Alternatives in 2026</title>
      <dc:creator>Pete Miloravac</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:51:23 +0000</pubDate>
      <link>https://dev.to/semaphore/best-github-actions-alternatives-in-2026-48ci</link>
      <guid>https://dev.to/semaphore/best-github-actions-alternatives-in-2026-48ci</guid>
      <description>&lt;p&gt;GitHub Actions became the default CI/CD choice for millions of repositories simply because it’s built into GitHub. But “default” and “best” are not the same thing — and in 2026, the gap between the two has gotten harder to ignore. A ten-hour outage in July, a supply-chain attack that backdoored over 5,500 repositories in May, and a steady stream of developer complaints about pricing, runner management, and log usability have sent teams looking for alternatives.&lt;/p&gt;

&lt;p&gt;This guide breaks down the best GitHub Actions alternatives in 2026, why GitHub Actions is falling short for a growing number of teams, and how to choose the right replacement for your workflow.&lt;/p&gt;

&lt;h2 id="tldr-best-github-actions-alternatives-in-2026"&gt;&lt;strong&gt;TL;DR: Best GitHub Actions Alternatives in 2026&lt;/strong&gt;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semaphore&lt;/strong&gt; — Best overall. Agent-native CI/CD that’s roughly 2x faster and cheaper than GitHub Actions, with self-maintaining pipelines built for teams using coding agents (Claude Code, Codex, Cursor, etc.).&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;CircleCI&lt;/strong&gt; — Best for teams that need deep parallelism and orchestration at enterprise scale.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;GitLab CI/CD&lt;/strong&gt; — Best if you want CI/CD bundled into a broader all-in-one DevOps platform.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Jenkins&lt;/strong&gt; — Best (only) if you specifically need full self-hosted control and don’t mind the maintenance overhead — more a legacy fallback than a modern alternative.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="where-github-actions-falls-short"&gt;&lt;strong&gt;Where GitHub Actions Falls Short&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;GitHub Actions works fine for small projects with simple workflows. Problems tend to show up as soon as a team scales, needs predictable reliability, or starts running more automated, agent-driven work through its pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability incidents are no longer rare.&lt;/strong&gt; On July 9, 2026, GitHub Actions suffered a major outage — GitHub’s own status page confirmed delayed and failed job starts on GitHub-hosted runners for over ten hours, from 03:29 to 13:39 UTC, caused by an unhealthy backend provisioning service. Developers on Hacker News and Reddit have documented a pattern of similar incidents throughout the year, with one engineer summing up the sentiment bluntly: teams “cannot deploy for days &lt;a href="https://www.youtube.com/watch?v=nJ4zhHhEiNs&amp;amp;t=255" rel="noopener noreferrer"&gt;because GitHub Actions is down&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supply-chain security is now a real, demonstrated risk.&lt;/strong&gt; In May 2026, a campaign dubbed “&lt;a href="https://www.securityweek.com/over-5500-github-repositories-infected-in-megalodon-supply-chain-attack/" rel="noopener noreferrer"&gt;Megalodon&lt;/a&gt;” pushed malicious GitHub Actions workflows to 5,561 repositories in under six hours, using workflow_dispatch triggers to plant dormant backdoors that could be activated later via stolen tokens. This came on top of a &lt;a href="https://www.legitsecurity.com/blog/github-privilege-escalation-vulnerability" rel="noopener noreferrer"&gt;documented class&lt;/a&gt; of workflow_run privilege-escalation vulnerabilities that let attackers exploit elevated permissions to tamper with releases, tags, and artifacts. GitHub published its own &lt;a href="https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/" rel="noopener noreferrer"&gt;2026 security roadmap&lt;/a&gt; acknowledging these exact exploit categories: untrusted code execution, unobserved malicious workflows, and over-permissioned credential exposure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency limits and runner management get painful fast.&lt;/strong&gt; Free and lower tiers cap concurrent jobs, and once you outgrow GitHub-hosted runners, self-hosting your own comes with real infrastructure and maintenance overhead — the opposite of what most teams want from a “managed” CI/CD product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usage-based pricing is hard to forecast.&lt;/strong&gt; Costs scale with runner minutes and job complexity, and teams frequently report surprise bills once test suites, matrix builds, or agent-driven runs multiply the number of jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log and debugging UX wasn’t built for today’s workflows.&lt;/strong&gt; Parallel jobs and increasingly automated pipelines (including those driven by coding agents) produce log output that’s genuinely hard to parse — a recurring complaint across developer forums.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You’re locked into the GitHub ecosystem.&lt;/strong&gt; Workflows, secrets, and runner configuration are tightly coupled to GitHub itself, which makes any future migration — to a different Git host, a different CI provider, or a hybrid setup — more expensive than it should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real support is expensive and hard to get to.&lt;/strong&gt; GitHub’s included Enterprise support is 24/5, web-ticket-only, with no guaranteed response times. &lt;a href="https://github.com/enterprise/premium-support" rel="noopener noreferrer"&gt;Actual SLA-backed support&lt;/a&gt; — phone callbacks, screen-share troubleshooting, and a named Customer Reliability Engineer — only exists behind the Premium and Premium Plus add-ons, and GitHub does not publish pricing for either; you have to contact sales. Customers who have gone through that process report quotes in the tens of thousands of dollars annually just to get a real SLA on Actions issues.&lt;/p&gt;

&lt;h2 id="comparison-table-github-actions-vs-the-alternatives"&gt;&lt;strong&gt;Comparison Table: GitHub Actions vs. the Alternatives&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;.semaphore-comparison-embed {&lt;br&gt;
    --font-down-1: 0.889rem;&lt;br&gt;
    --font-down-2: 0.79rem;&lt;br&gt;
    --font-down-3: 0.702rem;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--color-cream: #F7F3EC;
--color-blue: #2865DE;
--color-ink: #0D1421;
--color-green: #16A34A;
--color-red: #B91C1C;
--color-border: #c8cbd0;
--color-soft-border: #e5e7eb;

box-sizing: border-box;
color: var(--color-ink);
container-type: inline-size;
font-family: inherit;
width: 100%;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed *,&lt;br&gt;
  .semaphore-comparison-embed *::before,&lt;br&gt;
  .semaphore-comparison-embed *::after {&lt;br&gt;
    box-sizing: inherit;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .comparison-card {&lt;br&gt;
    min-width: 0;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .comparison-table-wrap {&lt;br&gt;
    overflow-x: auto;&lt;br&gt;
    width: 100%;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .comparison-table {&lt;br&gt;
    border: 1px solid var(--color-border);&lt;br&gt;
    border-radius: 14px;&lt;br&gt;
    border-collapse: separate;&lt;br&gt;
    border-spacing: 0;&lt;br&gt;
    font-size: var(--font-down-1);&lt;br&gt;
    min-width: 0;&lt;br&gt;
    overflow: hidden;&lt;br&gt;
    table-layout: fixed;&lt;br&gt;
    width: 100%;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed col:first-child {&lt;br&gt;
    width: 25%;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed col:not(:first-child) {&lt;br&gt;
    width: 18.75%;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed th {&lt;br&gt;
    background: var(--color-cream);&lt;br&gt;
    color: var(--color-ink);&lt;br&gt;
    font-size: var(--font-down-2);&lt;br&gt;
    font-weight: 800;&lt;br&gt;
    letter-spacing: 0;&lt;br&gt;
    overflow-wrap: anywhere;&lt;br&gt;
    padding: 0.875rem;&lt;br&gt;
    text-align: left;&lt;br&gt;
    text-transform: uppercase;&lt;br&gt;
    vertical-align: top;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed th:first-child {&lt;br&gt;
    border-top-left-radius: 14px;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed th:last-child {&lt;br&gt;
    border-top-right-radius: 14px;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed td {&lt;br&gt;
    border-bottom: 1px solid var(--color-soft-border);&lt;br&gt;
    font-weight: 700;&lt;br&gt;
    line-height: 1.35;&lt;br&gt;
    overflow-wrap: anywhere;&lt;br&gt;
    padding: 0.875rem;&lt;br&gt;
    vertical-align: top;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed tbody tr:last-child th,&lt;br&gt;
  .semaphore-comparison-embed tbody tr:last-child td {&lt;br&gt;
    border-bottom: 0;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .feature-cell {&lt;br&gt;
    background: #fff;&lt;br&gt;
    border-bottom: 1px solid var(--color-soft-border);&lt;br&gt;
    color: rgba(13, 20, 33, 0.78);&lt;br&gt;
    font-size: var(--font-down-2);&lt;br&gt;
    line-height: 1.3;&lt;br&gt;
    text-transform: none;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed td:nth-child(2) {&lt;br&gt;
    background: rgba(40, 101, 222, 0.06);&lt;br&gt;
    box-shadow: inset 3px 0 0 var(--color-blue);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .status {&lt;br&gt;
    border-radius: 999px;&lt;br&gt;
    display: inline-block;&lt;br&gt;
    font-size: var(--font-down-3);&lt;br&gt;
    font-weight: 800;&lt;br&gt;
    line-height: 1;&lt;br&gt;
    margin-right: 0.375rem;&lt;br&gt;
    padding: 0.3125rem 0.5rem;&lt;br&gt;
    text-transform: uppercase;&lt;br&gt;
    vertical-align: 0.0625rem;&lt;br&gt;
    white-space: nowrap;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .status-yes {&lt;br&gt;
    background: rgba(22, 163, 74, 0.12);&lt;br&gt;
    color: var(--color-green);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .status-no {&lt;br&gt;
    background: rgba(185, 28, 28, 0.1);&lt;br&gt;
    color: var(--color-red);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.semaphore-comparison-embed .status-limited {&lt;br&gt;
    background: var(--color-cream);&lt;br&gt;
    color: rgba(13, 20, 33, 0.72);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;@container (max-width: 57.5rem) {&lt;br&gt;
    .semaphore-comparison-embed th,&lt;br&gt;
    .semaphore-comparison-embed td {&lt;br&gt;
      padding: 0.75rem;&lt;br&gt;
    }&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;@container (max-width: 42rem) {&lt;br&gt;
    .semaphore-comparison-embed .comparison-table-wrap {&lt;br&gt;
      overflow-x: visible;&lt;br&gt;
    }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.semaphore-comparison-embed .comparison-table {
  border: 0;
  border-radius: 0;
}

.semaphore-comparison-embed .comparison-table,
.semaphore-comparison-embed thead,
.semaphore-comparison-embed tbody,
.semaphore-comparison-embed tr,
.semaphore-comparison-embed th,
.semaphore-comparison-embed td {
  display: block;
  width: 100%;
}

.semaphore-comparison-embed thead {
  clip: rect(0 0 0 0);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

.semaphore-comparison-embed tbody {
  display: grid;
  gap: 0.75rem;
}

.semaphore-comparison-embed tr {
  border: 1px solid var(--color-soft-border);
  border-radius: 14px;
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding: 0.625rem;
}

.semaphore-comparison-embed .feature-cell {
  border-bottom: 1px solid var(--color-soft-border);
  font-size: var(--font-down-1);
  grid-column: 1 / -1;
  padding: 0 0 0.5rem;
}

.semaphore-comparison-embed td {
  background: var(--color-cream);
  border: 0;
  border-radius: 10px;
  min-width: 0;
  padding: 0.625rem;
}

.semaphore-comparison-embed td:nth-child(2) {
  background: rgba(40, 101, 222, 0.08);
  box-shadow: inset 3px 0 0 var(--color-blue);
}

.semaphore-comparison-embed td::before {
  color: rgba(13, 20, 33, 0.62);
  content: attr(data-provider);
  display: block;
  font-size: var(--font-down-3);
  font-weight: 800;
  margin-bottom: 0.25rem;
  text-transform: uppercase;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;@container (max-width: 30rem) {&lt;br&gt;
    .semaphore-comparison-embed tr {&lt;br&gt;
      gap: 0.4375rem;&lt;br&gt;
      padding: 0.5rem;&lt;br&gt;
    }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.semaphore-comparison-embed td {
  padding: 0.5rem;
}

.semaphore-comparison-embed .status {
  margin-bottom: 0.25rem;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; (max-width: 57.5rem) {&lt;br&gt;
    .semaphore-comparison-embed th,&lt;br&gt;
    .semaphore-comparison-embed td {&lt;br&gt;
      padding: 0.75rem;&lt;br&gt;
    }&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; (max-width: 42rem) {&lt;br&gt;
    .semaphore-comparison-embed .comparison-table-wrap {&lt;br&gt;
      overflow-x: visible;&lt;br&gt;
    }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.semaphore-comparison-embed .comparison-table {
  border: 0;
  border-radius: 0;
}

.semaphore-comparison-embed .comparison-table,
.semaphore-comparison-embed thead,
.semaphore-comparison-embed tbody,
.semaphore-comparison-embed tr,
.semaphore-comparison-embed th,
.semaphore-comparison-embed td {
  display: block;
  width: 100%;
}

.semaphore-comparison-embed thead {
  clip: rect(0 0 0 0);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

.semaphore-comparison-embed tbody {
  display: grid;
  gap: 0.75rem;
}

.semaphore-comparison-embed tr {
  border: 1px solid var(--color-soft-border);
  border-radius: 14px;
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding: 0.625rem;
}

.semaphore-comparison-embed .feature-cell {
  border-bottom: 1px solid var(--color-soft-border);
  font-size: var(--font-down-1);
  grid-column: 1 / -1;
  padding: 0 0 0.5rem;
}

.semaphore-comparison-embed td {
  background: var(--color-cream);
  border: 0;
  border-radius: 10px;
  min-width: 0;
  padding: 0.625rem;
}

.semaphore-comparison-embed td:nth-child(2) {
  background: rgba(40, 101, 222, 0.08);
  box-shadow: inset 3px 0 0 var(--color-blue);
}

.semaphore-comparison-embed td::before {
  color: rgba(13, 20, 33, 0.62);
  content: attr(data-provider);
  display: block;
  font-size: var(--font-down-3);
  font-weight: 800;
  margin-bottom: 0.25rem;
  text-transform: uppercase;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; (max-width: 30rem) {&lt;br&gt;
    .semaphore-comparison-embed tr {&lt;br&gt;
      gap: 0.4375rem;&lt;br&gt;
      padding: 0.5rem;&lt;br&gt;
    }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.semaphore-comparison-embed td {
  padding: 0.5rem;
}

.semaphore-comparison-embed .status {
  margin-bottom: 0.25rem;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;table&amp;gt;
    &amp;lt;colgroup&amp;gt;
      &amp;lt;col&amp;gt;
      &amp;lt;col&amp;gt;
      &amp;lt;col&amp;gt;
      &amp;lt;col&amp;gt;
      &amp;lt;col&amp;gt;
    &amp;lt;/colgroup&amp;gt;
    &amp;lt;thead&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Criteria&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Semaphore&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;CircleCI&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;GitLab CI/CD&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Jenkins&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Avg. build time (benchmark)&amp;lt;sup&amp;gt;6&amp;lt;/sup&amp;gt;&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;5m 01s&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;13m 18s&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;11m 15s&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Not benchmarked (self-hosted, hardware-dependent)&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Cost per job (benchmark)&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;$0.04&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;$0.08&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;$0.11&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Infrastructure cost only (no per-job pricing)&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Agent-native / AI coding agent support&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;Built-in (sem-ai)&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Limited&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Limited&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;No&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Self-hosted option&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;Open source&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Enterprise only&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;Default&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Open source&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;Fully&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;No&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Core only&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Enterprise security certs&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;SOC 2 Type II, ISO 27001&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;SOC 2&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;SOC 2, ISO 27001&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Depends on self-managed setup&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Setup / migration effort from GHA&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;Low (guided migration)&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Medium&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Medium&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;High&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Monorepo support&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Limited&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Limited&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Manual/plugin-dependent&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Built-in flaky test detection&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;Cloud&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;No&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;No&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;No&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;SSO (SAML / Okta / LDAP)&amp;lt;/th&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;Cloud&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Enterprise only&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Enterprise only&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Plugin-dependent&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
  &amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2 id="the-best-github-actions-alternatives-ranked"&gt;&lt;strong&gt;The Best GitHub Actions Alternatives, Ranked&lt;/strong&gt;&lt;/h2&gt;

&lt;h3 id="1-semaphore-best-overall-best-for-ai-coding-agents"&gt;1. Semaphore — Best Overall, Best for AI Coding Agents&lt;/h3&gt;

&lt;p&gt;Semaphore has repositioned itself as agent-native CI: “Tell your agent what you want. It runs your CI.” Instead of hand-writing YAML, developers (or their coding agents) can tell Semaphore what they need, and it configures and runs the pipeline — including pre-push validation in production-identical sandboxes and self-maintaining pipelines that identify and fix flaky tests as your codebase grows.&lt;/p&gt;

&lt;p&gt;This matters more than it might sound. In a recent interview, Semaphore’s Head of Product Marko Gaćeša made the case that testing — not writing code — is now the real bottleneck in software delivery, especially as teams run multiple coding agents in parallel: “I see the biggest bottleneck being testing… [pipelines] are going to look significantly different” as agents need feedback in minutes, &lt;a href="https://www.youtube.com/watch?v=nJ4zhHhEiNs&amp;amp;t=420" rel="noopener noreferrer"&gt;not the 20-30 minute cycles teams tolerated before&lt;/a&gt;. That’s a direct response to the workflows GitHub Actions wasn’t designed for.&lt;/p&gt;

&lt;p&gt;On raw performance, Semaphore’s own third-party-reproducible benchmark (10 runs, identical Ruby on Rails workload, cache warm) shows an average build time of 5m 01s versus GitHub Actions’ 9m 44s — a 94.48% speed advantage — at &lt;a href="https://semaphore.io/semaphore-ci-cd-benchmark-performance-and-cost-analysis" rel="noopener noreferrer"&gt;$0.04 per job versus $0.06&lt;/a&gt; for GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Agent-native pipeline setup (sem-ai), self-hosted or cloud, fully open source and self-hostable, SOC 2 Type II + ISO 27001 certified, transparent pay-as-you-go pricing with $15/month free usage and no seat or idle costs, guided migration tooling from GitHub Actions, GitLab CI, CircleCI, and Jenkins.&lt;/p&gt;

&lt;p&gt;Support is also a real differentiator, not just a feature checkbox. Where GitHub keeps its real support pricing behind a sales call, Semaphore publishes every tier on its &lt;a href="https://semaphore.io/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;: a free tier for basic help, $50/month for email support, $250/month for email plus Slack, and up to $750/month for SLA-backed plans with response times as fast as 1 hour on urgent issues (with optional 24/7 coverage) — plus add-on customer success and engineering hours for teams that need deeper, hands-on help. You pick the level your team actually needs and see the exact price upfront, instead of negotiating a five-figure contract just to get someone on the phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams already using (or planning to use) AI coding agents, teams that got burned by a GitHub Actions outage or security incident, and teams that want faster feedback loops without a corresponding cost increase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Considerations:&lt;/strong&gt; Smaller ecosystem of pre-built community actions/integrations than GitHub’s marketplace, since Semaphore is a newer entrant to public awareness despite being in the market since 2014.&lt;/p&gt;

&lt;h4 id="what-teams-who-switched-from-github-actions-are-saying"&gt;&lt;strong&gt;What Teams Who Switched From GitHub Actions Are Saying&lt;/strong&gt;&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;“Since moving to Semaphore from GitHub Actions, our CI pipeline has been stable and had more consistent run times, this allowed us to detect failures and flaky specs faster.”&lt;/em&gt; — Amin Ben Slimen, Senior Software Engineer at klarx&lt;/li&gt;



&lt;li&gt;
&lt;em&gt;“It’s cheap, it’s easy to understand, and the support team is quick to assist if you need help. Honestly, after trying GitHub Actions, CircleCI, Octopus, Jenkins, and more, this clearly stands out to me as the better option.”&lt;/em&gt; — Fredrik August Madsen-Malmo, Head of DevOps at Kvist&lt;/li&gt;



&lt;li&gt;
&lt;em&gt;“Semaphore is an incredibly easy-to-use CI/CD platform. Integrating my Ruby on Rails applications to use it is always very easy to do.”&lt;/em&gt; — Lorenzo Zabot, Backend Engineer at Vox Group&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="2-circleci-best-for-enterprise-scale-parallelism"&gt;2. CircleCI — Best for Enterprise-Scale Parallelism&lt;/h3&gt;

&lt;p&gt;CircleCI remains one of the most established cloud CI/CD platforms, particularly for teams running large test suites that benefit from aggressive parallelization and orchestration features like dynamic config and matrix jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Strong parallelism and caching, orchestration for complex multi-service pipelines, self-hosted runners for enterprise plans, broad language/framework support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Larger engineering orgs with complex, multi-repo build graphs that need fine-grained control over job orchestration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Considerations:&lt;/strong&gt; In the same benchmark referenced above, CircleCI was the slowest and most cost-variable of the tools tested — averaging 13m 18s per build (165% slower than Semaphore) at $0.08 per job, with individual run times swinging &lt;a href="https://semaphore.io/semaphore-ci-cd-benchmark-performance-and-cost-analysis" rel="noopener noreferrer"&gt;as high as 17 minutes&lt;/a&gt;. Pricing and plan tiers can also get complex at scale.&lt;/p&gt;

&lt;h3 id="3-gitlab-ci-cd-best-all-in-one-devops-platform"&gt;3. GitLab CI/CD — Best All-in-One DevOps Platform&lt;/h3&gt;

&lt;p&gt;If your team already runs on GitLab, or wants source control, CI/CD, security scanning, and project management under one roof, GitLab CI/CD is a natural alternative. It’s configured with a familiar .gitlab-ci.yml file and integrates tightly with merge requests and built-in runners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Native integration with GitLab’s broader DevSecOps suite, built-in container registry, tight merge-request integration, both SaaS and self-managed deployment options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams willing to consolidate their whole toolchain onto GitLab, or teams already there who want to reduce the number of vendors they manage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Considerations:&lt;/strong&gt; Migrating your Git hosting itself (not just CI) is a bigger lift than swapping CI providers alone. Build times in the same benchmark averaged 11m 15s, and cost per job was the highest of all tools &lt;a href="https://semaphore.io/semaphore-ci-cd-benchmark-performance-and-cost-analysis" rel="noopener noreferrer"&gt;tested at $0.11&lt;/a&gt; — nearly 3x Semaphore’s cost.&lt;/p&gt;

&lt;h3 id="4-jenkins-the-legacy-self-hosted-option"&gt;4. Jenkins — The Legacy Self-Hosted Option&lt;/h3&gt;

&lt;p&gt;Jenkins predates all of the above and remains the most customizable, plugin-extensible CI server on the market — entirely because it’s open source and infinitely scriptable. Some teams with very specific compliance or air-gapped infrastructure requirements still choose it deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt; Enormous plugin ecosystem, full control over infrastructure and build environment, no vendor lock-in, free to run (excluding your own hosting costs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Organizations with strict on-premises requirements and dedicated platform engineering resources to maintain it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Considerations:&lt;/strong&gt; This isn’t really a “GitHub Actions alternative” so much as a different category of tool — you’re trading GitHub’s reliability and security problems for the maintenance burden, Groovy scripting complexity, and plugin management overhead Jenkins is well known for. Most teams migrating away from GitHub Actions are looking for less operational overhead, not more.&lt;/p&gt;

&lt;h2 id="how-to-choose-the-right-github-actions-alternative"&gt;&lt;strong&gt;How to Choose the Right GitHub Actions Alternative&lt;/strong&gt;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If you’re already using (or planning to use) AI coding agents for development,&lt;/strong&gt; prioritize a CI platform built around that workflow rather than retrofitting one designed for manual pushes. Semaphore is purpose-built for this.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;If your primary pain is speed and unpredictable cost,&lt;/strong&gt; compare benchmarked build times and per-job pricing directly rather than relying on marketing claims — the numbers above are independently reproducible.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;If your primary pain is a recent outage or security incident,&lt;/strong&gt; weigh how each vendor handles incident transparency, and whether the platform is open source enough for you to self-host as a fallback.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;If you need to stay inside a single platform&lt;/strong&gt; for compliance or simplicity, GitLab CI/CD is the more natural move than introducing a new best-of-breed tool.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;If you have hard on-premises requirements and the engineering headcount to support it,&lt;/strong&gt; Jenkins remains viable — just budget for the maintenance cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ready to see the difference for yourself?&lt;/strong&gt; &lt;a href="https://id.semaphoreci.com/signup" rel="noopener noreferrer"&gt;Start free with Semaphore&lt;/a&gt; — no credit card required, $15 of usage included every month.&lt;/p&gt;

&lt;h2 id="frequently-asked-questions"&gt;Frequently Asked Questions&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;strong&gt;What is the best alternative to GitHub Actions in 2026?&lt;/strong&gt;&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;Semaphore is the strongest overall alternative for most teams — it’s benchmarked as roughly 2x faster and cheaper than GitHub Actions, and it’s purpose-built for teams using AI coding agents, which is where CI/CD workflows are heading. CircleCI and GitLab CI/CD are also strong options depending on whether you prioritize enterprise-scale parallelism or an all-in-one platform.&lt;/p&gt;  &lt;strong&gt;&lt;strong&gt;Why are teams moving off GitHub Actions?&lt;/strong&gt;&lt;/strong&gt; &lt;p&gt;The three most common reasons are reliability (including the ~10-hour outage on July 9, 2026), security incidents like the Megalodon supply-chain attack that backdoored over 5,500 repositories, and cost/performance — GitHub Actions is measurably slower and more expensive per job than newer alternatives in independent benchmarks.&lt;/p&gt;  &lt;strong&gt;&lt;strong&gt;Is switching from GitHub Actions to Semaphore difficult?&lt;/strong&gt;&lt;/strong&gt; &lt;p&gt;No — Semaphore provides guided migration tooling for teams moving from GitHub Actions, GitLab CI, CircleCI, or Jenkins, and most teams are able to migrate core pipelines in well under an hour.&lt;/p&gt;  &lt;strong&gt;&lt;strong&gt;Is Semaphore open source?&lt;/strong&gt;&lt;/strong&gt; &lt;p&gt;Yes. Semaphore is fully open source and self-hostable, in addition to being available as a managed cloud service.&lt;/p&gt;  &lt;strong&gt;&lt;strong&gt;Does Semaphore support the same features as GitHub Actions?&lt;/strong&gt;&lt;/strong&gt; &lt;p&gt;Semaphore covers the core CI/CD workflow — build, test, deploy — plus additions like pre-push sandbox validation and self-maintaining pipelines. Its community action/integration marketplace is smaller than GitHub’s, since GitHub Actions has a multi-year head start, but core CI/CD functionality and migration paths are well covered.&lt;/p&gt;  

&lt;p&gt;The post &lt;a href="https://semaphore.io/best-github-actions-alternatives-in-2026" rel="noopener noreferrer"&gt;Best GitHub Actions Alternatives in 2026&lt;/a&gt; appeared first on &lt;a href="https://semaphore.io" rel="noopener noreferrer"&gt;Semaphore&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cicd</category>
    </item>
    <item>
      <title>What CI/CD strategies work for embedded or IoT projects that require hardware testing?</title>
      <dc:creator>Pete Miloravac</dc:creator>
      <pubDate>Thu, 30 Apr 2026 10:48:53 +0000</pubDate>
      <link>https://dev.to/semaphore/what-cicd-strategies-work-for-embedded-or-iot-projects-that-require-hardware-testing-39p2</link>
      <guid>https://dev.to/semaphore/what-cicd-strategies-work-for-embedded-or-iot-projects-that-require-hardware-testing-39p2</guid>
      <description>&lt;p&gt;Embedded and IoT teams face a very different CI/CD reality than traditional SaaS teams. While most continuous integration and continuous delivery pipelines assume everything can run in the cloud, embedded systems depend on physical hardware, constrained environments, and real world signals.&lt;/p&gt;

&lt;p&gt;If you search forums like Reddit (r/embedded, r/devops), Stack Overflow, or vendor communities, the same questions keep appearing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I run CI tests when I need actual hardware?&lt;/li&gt;
&lt;li&gt;How do I scale hardware testing across teams?&lt;/li&gt;
&lt;li&gt;How do I avoid flaky tests caused by devices?&lt;/li&gt;
&lt;li&gt;Can I still use modern CI/CD tools or do I need something custom?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide walks through practical CI/CD strategies used by engineering teams building firmware, IoT platforms, and hardware dependent systems. It focuses on approaches that scale, reduce cost, and maintain reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CI/CD is harder for embedded and IoT projects
&lt;/h2&gt;

&lt;p&gt;Unlike pure software systems, embedded pipelines must deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Physical device availability&lt;/li&gt;
&lt;li&gt;Hardware state and reset issues&lt;/li&gt;
&lt;li&gt;Long flashing and boot cycles&lt;/li&gt;
&lt;li&gt;Non deterministic behavior (timing, sensors, connectivity)&lt;/li&gt;
&lt;li&gt;Limited parallelization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a mismatch with traditional CI/CD platforms that expect fast, stateless, fully virtualized execution.&lt;/p&gt;

&lt;p&gt;For engineering leaders, this often results in slower pipelines, higher costs, and fragile automation. The goal is not to force cloud native assumptions onto hardware, but to design a hybrid pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core strategy: split your pipeline by test layers
&lt;/h2&gt;

&lt;p&gt;The most effective approach seen across teams is to separate tests into layers, minimizing hardware usage to only what is necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fast feedback layer (no hardware)
&lt;/h3&gt;

&lt;p&gt;Run as much as possible without devices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests for firmware logic&lt;/li&gt;
&lt;li&gt;Static analysis (clang tidy, cppcheck)&lt;/li&gt;
&lt;li&gt;Build validation&lt;/li&gt;
&lt;li&gt;Simulation or emulation (QEMU, Renode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example Semaphore pipeline block:&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="s"&gt;v1.0&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Firmware CI&lt;/span&gt;

&lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and unit tests&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
          &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make build&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This layer should cover 70 to 90 percent of your test surface. It keeps pipelines fast and cost effective.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Hardware in the loop testing (HIL)
&lt;/h3&gt;

&lt;p&gt;Only after passing fast checks should jobs use real devices.&lt;/p&gt;

&lt;p&gt;Typical setup discussed in forums:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;USB connected device farms&lt;/li&gt;
&lt;li&gt;Network controlled power switches&lt;/li&gt;
&lt;li&gt;Raspberry Pi or similar acting as device controllers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example structure:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hardware tests&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prologue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run on device farm&lt;/span&gt;
          &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./scripts/flash_device.sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./scripts/run_integration_tests.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Semaphore, you can integrate self hosted agents that have direct access to hardware, allowing your pipeline to orchestrate physical tests while keeping control centralized.&lt;/p&gt;

&lt;p&gt;This hybrid model is critical for teams that have outgrown default CI tools that cannot reliably interface with hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing a reliable device farm
&lt;/h2&gt;

&lt;p&gt;A common pain point across discussions is flaky hardware tests. The root cause is usually poor device management.&lt;/p&gt;

&lt;p&gt;Best practices:&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolate devices per job
&lt;/h3&gt;

&lt;p&gt;Avoid shared hardware when possible. If sharing is required, implement locking mechanisms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate reset and recovery
&lt;/h3&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smart power switches&lt;/li&gt;
&lt;li&gt;USB relay boards&lt;/li&gt;
&lt;li&gt;Watchdog scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example reset script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Power cycle device&lt;/span&gt;
curl http://power-switch.local/off
&lt;span class="nb"&gt;sleep &lt;/span&gt;2
curl http://power-switch.local/on
&lt;span class="nb"&gt;sleep &lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Make tests idempotent
&lt;/h3&gt;

&lt;p&gt;Each test run should not depend on previous device state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collect logs externally
&lt;/h3&gt;

&lt;p&gt;Stream logs via serial or network so failures can be diagnosed post run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Orchestrating hardware with CI/CD
&lt;/h2&gt;

&lt;p&gt;One of the biggest questions teams ask is: how do I connect CI/CD pipelines to real devices?&lt;/p&gt;

&lt;p&gt;The most robust approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use cloud CI/CD for orchestration&lt;/li&gt;
&lt;li&gt;Use self hosted runners or agents for hardware interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Semaphore, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running standard pipeline steps in the cloud&lt;/li&gt;
&lt;li&gt;Routing hardware jobs to agents inside your lab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps pipelines fast while maintaining control over physical infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling test flakiness and timing issues
&lt;/h2&gt;

&lt;p&gt;Forum discussions consistently highlight flaky tests as a major blocker.&lt;/p&gt;

&lt;p&gt;Strategies that work:&lt;/p&gt;

&lt;h3&gt;
  
  
  Add retry logic at the pipeline level
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add health checks before tests
&lt;/h3&gt;

&lt;p&gt;Ensure device is reachable and responsive before running tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use time budgets instead of strict timing
&lt;/h3&gt;

&lt;p&gt;Avoid asserting exact timing unless necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tag and isolate flaky tests
&lt;/h3&gt;

&lt;p&gt;Run them separately to avoid blocking the main pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parallelizing hardware testing
&lt;/h2&gt;

&lt;p&gt;Scaling is a key concern for engineering managers.&lt;/p&gt;

&lt;p&gt;Options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expanding device farms&lt;/li&gt;
&lt;li&gt;Sharding tests across devices&lt;/li&gt;
&lt;li&gt;Prioritizing critical test subsets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Device &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./run_tests.sh --group=1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Device &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./run_tests.sh --group=2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Semaphore allows parallel job execution, which is especially valuable when coordinating multiple hardware nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost optimization strategies
&lt;/h2&gt;

&lt;p&gt;Hardware pipelines can become expensive quickly.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Maximize non hardware test coverage&lt;/li&gt;
&lt;li&gt;Use on demand hardware instead of always on&lt;/li&gt;
&lt;li&gt;Optimize pipeline duration&lt;/li&gt;
&lt;li&gt;Avoid rerunning full hardware suites unnecessarily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Engineering teams often switch CI/CD platforms when costs become unpredictable or tied to inefficient pipelines. A system that allows fine grained control over execution and resource usage is critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to consider custom tooling vs CI/CD platforms
&lt;/h2&gt;

&lt;p&gt;Many teams ask whether they should build their own system.&lt;/p&gt;

&lt;p&gt;Build custom only if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your workflow is highly specialized&lt;/li&gt;
&lt;li&gt;You require deep hardware orchestration beyond standard pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, modern CI/CD platforms like Semaphore can handle orchestration while letting you customize the hardware layer.&lt;/p&gt;

&lt;p&gt;This balance avoids reinventing core CI/CD capabilities while still supporting embedded workflows.&lt;/p&gt;

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

&lt;p&gt;A production ready embedded CI/CD pipeline typically looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code commit triggers pipeline&lt;/li&gt;
&lt;li&gt;Build and unit tests run in parallel&lt;/li&gt;
&lt;li&gt;Simulation tests validate behavior&lt;/li&gt;
&lt;li&gt;Hardware tests run on device farm via self hosted agents&lt;/li&gt;
&lt;li&gt;Results aggregated and reported&lt;/li&gt;
&lt;li&gt;Deployment triggered if all checks pass&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This structure aligns with how high performing engineering teams reduce risk while maintaining delivery speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I run CI tests on real hardware?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Use a device farm connected to self hosted CI agents. The pipeline triggers scripts that flash firmware and execute tests on physical devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What tools are commonly used for hardware testing in CI?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Teams commonly use QEMU or Renode for simulation, and custom scripts combined with device controllers for real hardware testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I reduce flaky tests in IoT CI/CD?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Focus on device reset automation, idempotent tests, retries, and proper health checks before execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can CI/CD pipelines scale with hardware constraints?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yes, by parallelizing across device farms and minimizing reliance on hardware through simulation and unit testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is CI/CD worth it for embedded systems?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yes. Teams that invest in CI/CD for embedded systems see improvements in reliability, faster debugging, and more predictable releases.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://semaphore.io/what-ci-cd-strategies-work-for-embedded-or-iot-projects-that-require-hardware-testing" rel="noopener noreferrer"&gt;What CI/CD strategies work for embedded or IoT projects that require hardware testing?&lt;/a&gt; appeared first on &lt;a href="https://semaphore.io" rel="noopener noreferrer"&gt;Semaphore&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cicd</category>
    </item>
    <item>
      <title>How Does CI/CD Differ for Machine Learning Pipelines (MLOps)?</title>
      <dc:creator>Pete Miloravac</dc:creator>
      <pubDate>Thu, 23 Apr 2026 15:24:09 +0000</pubDate>
      <link>https://dev.to/semaphore/how-does-cicd-differ-for-machine-learning-pipelines-mlops-cjk</link>
      <guid>https://dev.to/semaphore/how-does-cicd-differ-for-machine-learning-pipelines-mlops-cjk</guid>
      <description>&lt;p&gt;For most engineering teams, CI/CD is already a solved problem—at least on the surface. You commit code, run tests, build artifacts, and deploy.&lt;/p&gt;

&lt;p&gt;But when teams start introducing machine learning into production systems, that familiar pipeline begins to break down.&lt;/p&gt;

&lt;p&gt;Across forums like Reddit (r/MachineLearning, r/devops), Stack Overflow, and Hacker News, the same questions come up repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“How do I version datasets in CI/CD?”&lt;/li&gt;
&lt;li&gt;“Why does my model degrade after deployment even though tests pass?”&lt;/li&gt;
&lt;li&gt;“How do I test something that learns from data?”&lt;/li&gt;
&lt;li&gt;“Should I deploy models the same way as application code?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial answers those questions with a practical lens. More importantly, it explains what engineering leaders need to rethink when adapting CI/CD pipelines for MLOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional CI/CD Breaks Down for Machine Learning
&lt;/h2&gt;

&lt;p&gt;In traditional software delivery, your pipeline is built around code determinism.&lt;/p&gt;

&lt;p&gt;Given the same input, your application produces the same output. Your CI/CD pipeline enforces this through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;Integration tests&lt;/li&gt;
&lt;li&gt;Build reproducibility&lt;/li&gt;
&lt;li&gt;Static artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Machine learning systems violate this assumption in three key ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data is a first-class dependency&lt;/li&gt;
&lt;li&gt;Outputs are probabilistic, not deterministic&lt;/li&gt;
&lt;li&gt;Performance degrades over time (data drift)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This fundamentally changes how you design continuous integration and continuous deployment.&lt;/p&gt;

&lt;p&gt;For engineering managers and CTOs, this is where pipelines often become fragile, slow, and expensive—especially when built on top of tools that were not designed for these workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Differences Between CI/CD and MLOps Pipelines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. What You Version: Code vs Code + Data + Models
&lt;/h3&gt;

&lt;p&gt;In a standard CI/CD pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You version application code&lt;/li&gt;
&lt;li&gt;Dependencies are managed via package managers&lt;/li&gt;
&lt;li&gt;Builds are reproducible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In MLOps, you must version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Training data&lt;/li&gt;
&lt;li&gt;Feature engineering logic&lt;/li&gt;
&lt;li&gt;Model artifacts&lt;/li&gt;
&lt;li&gt;Hyperparameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical approach is to combine Git with a data versioning tool like DVC.&lt;/p&gt;

&lt;p&gt;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;# Track dataset&lt;/span&gt;
dvc add data/training.csv

&lt;span class="c"&gt;# Push data to remote storage&lt;/span&gt;
dvc push

&lt;span class="c"&gt;# Commit metadata&lt;/span&gt;
git add data/training.csv.dvc .gitignore
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Track training dataset"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your CI/CD pipeline now needs to fetch not just code, but also the correct dataset version.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What You Test: Logic vs Behavior
&lt;/h3&gt;

&lt;p&gt;Traditional CI focuses on correctness:&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="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In machine learning, you test behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy thresholds&lt;/li&gt;
&lt;li&gt;Precision and recall&lt;/li&gt;
&lt;li&gt;Model drift&lt;/li&gt;
&lt;li&gt;Bias detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example test step in a pipeline:&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;assert&lt;/span&gt; &lt;span class="n"&gt;model_accuracy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.87&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model accuracy below threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This introduces a new challenge: tests can fail even when code hasn’t changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. What You Build: Binaries vs Experiments
&lt;/h3&gt;

&lt;p&gt;In traditional pipelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build once&lt;/li&gt;
&lt;li&gt;Deploy artifact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In MLOps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Train model&lt;/li&gt;
&lt;li&gt;Evaluate multiple experiments&lt;/li&gt;
&lt;li&gt;Select best candidate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your pipeline becomes iterative and branching.&lt;/p&gt;

&lt;p&gt;Example workflow:&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;blocks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Train models&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;train-xgboost&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;train-random-forest&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Evaluate&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compare-metrics&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy best model&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Deployment: Static Releases vs Continuous Retraining
&lt;/h3&gt;

&lt;p&gt;Traditional deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triggered by code changes&lt;/li&gt;
&lt;li&gt;Releases are versioned and stable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MLOps deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triggered by new data&lt;/li&gt;
&lt;li&gt;Models may be retrained daily or hourly&lt;/li&gt;
&lt;li&gt;Performance must be monitored continuously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where many teams struggle. They try to force data-driven workflows into code-driven pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing a CI/CD Pipeline for Machine Learning
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a practical pipeline using Semaphore.&lt;/p&gt;

&lt;p&gt;Semaphore is particularly well-suited here because it allows you to orchestrate complex workflows without introducing unnecessary pipeline overhead—critical for compute-heavy ML workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Reproducible Environment
&lt;/h3&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="s"&gt;v1.0&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ML Pipeline&lt;/span&gt;

&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;machine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;e1-standard-4&lt;/span&gt;
    &lt;span class="na"&gt;os_image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu2004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For ML, reproducibility is everything. Use Docker or pinned environments to avoid failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Fetch Data and Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fetch data&lt;/span&gt;
          &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dvc pull&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step is often missing in traditional pipelines—and is one of the main sources of confusion discussed in forums.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Train Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Train&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Train model&lt;/span&gt;
          &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python train.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Evaluate Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Evaluate&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Evaluate model&lt;/span&gt;
          &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python evaluate.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example evaluation script:&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;if&lt;/span&gt; &lt;span class="n"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.87&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model did not meet quality threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Conditional Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy model&lt;/span&gt;
          &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python deploy.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Semaphore, you can gate this step using promotions, approvals, or conditions—important for controlling risk in ML deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Treating Models Like Code Artifacts
&lt;/h3&gt;

&lt;p&gt;Models are not static. If you deploy them once and forget them, they will degrade.&lt;/p&gt;

&lt;p&gt;Fix: Add monitoring and retraining triggers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignoring Data Versioning
&lt;/h3&gt;

&lt;p&gt;Without versioned data, debugging becomes impossible.&lt;/p&gt;

&lt;p&gt;Fix: Use DVC, feature stores, or data snapshots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overloading CI with Training Jobs
&lt;/h3&gt;

&lt;p&gt;Training jobs can be expensive and slow.&lt;/p&gt;

&lt;p&gt;Fix: Separate lightweight CI from heavy training workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of Observability
&lt;/h3&gt;

&lt;p&gt;Traditional CI/CD tools focus on build logs—not model performance.&lt;/p&gt;

&lt;p&gt;Fix: Integrate monitoring and metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategic Implications for Engineering Leaders
&lt;/h2&gt;

&lt;p&gt;For decision makers, the shift to MLOps is not just technical—it affects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost structure&lt;/li&gt;
&lt;li&gt;Reliability&lt;/li&gt;
&lt;li&gt;Tooling decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams that succeed treat CI/CD for ML as a first-class system, not an extension of existing pipelines.&lt;/p&gt;

&lt;p&gt;This is where platforms like Semaphore position themselves differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexible pipeline orchestration for complex workflows&lt;/li&gt;
&lt;li&gt;Predictable performance at scale&lt;/li&gt;
&lt;li&gt;Cost efficiency compared to legacy tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Should You Adapt Your Pipeline?
&lt;/h2&gt;

&lt;p&gt;You likely need to rethink your CI/CD if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are deploying models to production&lt;/li&gt;
&lt;li&gt;Your pipelines are slowing down due to training workloads&lt;/li&gt;
&lt;li&gt;You cannot reproduce model results reliably&lt;/li&gt;
&lt;li&gt;CI/CD costs are increasing unpredictably&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the main difference between CI/CD and MLOps pipelines?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Traditional CI/CD focuses on deterministic code, while MLOps pipelines must handle data, probabilistic outputs, and continuous retraining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use standard CI/CD tools for machine learning?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yes, but most teams need to extend them significantly to support data versioning, model evaluation, and retraining workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you test machine learning models in CI/CD?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;By validating metrics such as accuracy, precision, recall, and monitoring for drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should model training run in CI?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Not always. Many teams separate training pipelines from CI to control cost and runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you deploy machine learning models safely?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Use staged rollouts, approval gates, and continuous monitoring.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://semaphore.io/how-does-ci-cd-differ-for-machine-learning-pipelines-mlops" rel="noopener noreferrer"&gt;How Does CI/CD Differ for Machine Learning Pipelines (MLOps)?&lt;/a&gt; appeared first on &lt;a href="https://semaphore.io" rel="noopener noreferrer"&gt;Semaphore&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cicd</category>
    </item>
    <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://dev.to/semaphore/what-is-mcp-really-545l</link>
      <guid>https://dev.to/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&amp;nbsp;&lt;code&gt;tools/list&lt;/code&gt;&amp;nbsp;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://dev.to/semaphore/lightweight-containers-with-docker-and-webassembly-19fl</link>
      <guid>https://dev.to/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://dev.to/semaphore/state-of-devops-2023-report-key-findings-and-insights-hl</link>
      <guid>https://dev.to/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://dev.to/semaphore/function-calling-integrate-your-gpt-chatbot-with-anything-3l5d</link>
      <guid>https://dev.to/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://dev.to/semaphore/word-embeddings-giving-your-chatbot-context-for-better-answers-156n</link>
      <guid>https://dev.to/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 &amp;quot;Create a CI pipeline that builds and uploads a Docker image to Docker Hub&amp;quot;, 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://dev.to/semaphore/breaking-free-from-the-cloud-with-mrsk-just-enough-orchestration-for-your-apps-48fe</link>
      <guid>https://dev.to/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 &amp;nbsp; IMAGE&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; COMMAND&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; CREATED &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; STATUS &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; PORTS&amp;nbsp; &amp;nbsp; &amp;nbsp; NAMES

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

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

ce4a5c31e6fc &amp;nbsp; tomfern/addressbook:f043325e3984ec245a94b21bd236afcc537a9739 &amp;nbsp; &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt; &amp;nbsp; 3 hours ago &amp;nbsp; &amp;nbsp; Exited &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt; 9 minutes ago&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 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://dev.to/semaphore/a-first-look-at-neon-a-postgres-database-that-branches-10e6</link>
      <guid>https://dev.to/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://dev.to/semaphore/getting-started-with-astro-nb7</link>
      <guid>https://dev.to/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://dev.to/semaphore/ruby-adds-support-for-webassembly-what-is-webassembly-and-how-it-benefits-ruby-devs-2825</link>
      <guid>https://dev.to/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>
  </channel>
</rss>
