<?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: Om Uphade</title>
    <description>The latest articles on DEV Community by Om Uphade (@omuphade).</description>
    <link>https://dev.to/omuphade</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%2Fuser%2Fprofile_image%2F3822500%2F1f42d5d6-4385-43f7-a73c-4f42f13054bd.png</url>
      <title>DEV Community: Om Uphade</title>
      <link>https://dev.to/omuphade</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omuphade"/>
    <language>en</language>
    <item>
      <title>Building a Real Multi-Step AI Agent with Gemini Function Calling (Not Just a Chatbot)</title>
      <dc:creator>Om Uphade</dc:creator>
      <pubDate>Thu, 01 Oct 2026 20:38:45 +0000</pubDate>
      <link>https://dev.to/omuphade/building-a-real-multi-step-ai-agent-with-gemini-function-calling-not-just-a-chatbot-3nl6</link>
      <guid>https://dev.to/omuphade/building-a-real-multi-step-ai-agent-with-gemini-function-calling-not-just-a-chatbot-3nl6</guid>
      <description>&lt;p&gt;Most "AI chat" tutorials stop at single-shot request/response. You send a message, the model replies, done. That's a chatbot — useful, but limited.&lt;/p&gt;

&lt;p&gt;What makes something an &lt;em&gt;agent&lt;/em&gt; instead is a loop: the model can call a tool, look at what it got back, and decide whether it needs another tool before it's ready to actually answer you.&lt;/p&gt;

&lt;p&gt;In this post I'll walk through how to build that loop using Google Gemini's function-calling API, which has a genuinely free tier — no credit card needed to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core loop
&lt;/h2&gt;

&lt;p&gt;Here's the shape of it, stripped down:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runAgentLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// model gave a final answer, we're done&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionCalls&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;toolResult&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;runTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toolResult&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// loop continues - model sees the tool results and decides what's next&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;That's genuinely the whole idea. Everything else — auth, streaming, persistence — is infrastructure around this loop, not the loop itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a step limit matters
&lt;/h2&gt;

&lt;p&gt;Without a cap, a confused model could theoretically call tools in a loop forever. &lt;code&gt;MAX_STEPS&lt;/code&gt; is a safety valve, not an optimization — it guarantees the request always terminates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming tool calls in real time
&lt;/h2&gt;

&lt;p&gt;Rather than making the user wait for the whole loop to finish, you can stream each step as it happens using Server-Sent Events — the user sees "calling calculator... got 564... calling get_current_time..." live, instead of a blank spinner.&lt;/p&gt;

&lt;p&gt;I recorded a short walkthrough showing this running live against a real Gemini key, if it's easier to see than read:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Defining a tool
&lt;/h2&gt;

&lt;p&gt;Gemini's function-calling schema is close to standard JSON Schema:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;calculator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Evaluate a basic arithmetic expression.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model sees this schema and decides on its own when calling this tool would help answer the user's question — you don't have to detect intent yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I packaged a complete working version of this — including auth, guest mode, and three working example tools — as a starter kit, if you want a working reference rather than building from scratch:&lt;br&gt;
&lt;a href="https://omuphade.gumroad.com/l/pfkmad" rel="noopener noreferrer"&gt;https://omuphade.gumroad.com/l/pfkmad&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of the architecture decisions in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Fixing a 20s Kubernetes API Lag in 15 Minutes with OpenTelemetry</title>
      <dc:creator>Om Uphade</dc:creator>
      <pubDate>Sun, 19 Jul 2026 17:56:43 +0000</pubDate>
      <link>https://dev.to/omuphade/fixing-a-20s-kubernetes-api-lag-in-15-minutes-with-opentelemetry-n4m</link>
      <guid>https://dev.to/omuphade/fixing-a-20s-kubernetes-api-lag-in-15-minutes-with-opentelemetry-n4m</guid>
      <description>&lt;p&gt;Our Kubernetes API was suddenly taking 20 seconds to respond, but traditional pod metrics showed healthy CPU and memory. I needed true observability to find the root cause without rewriting code or paying Datadog's $0.05 per custom metric surcharge. &lt;/p&gt;

&lt;p&gt;I chose SigNoz for a unified metrics, logs, and traces backend, and the OpenTelemetry Operator to auto-instrument our cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Setup
&lt;/h3&gt;

&lt;p&gt;First, I installed SigNoz directly into our cluster using Helm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add signoz https://charts.signoz.io
kubectl create ns platform
helm &lt;span class="nt"&gt;--namespace&lt;/span&gt; platform &lt;span class="nb"&gt;install &lt;/span&gt;my-release signoz/signoz
Next, to get data flowing without touching our Java &lt;span class="nb"&gt;source &lt;/span&gt;code, I installed the OpenTelemetry Operator and simply added this annotation to our application&lt;span class="s1"&gt;'s deployment manifest:
template:
  metadata:
    annotations:
      instrumentation.opentelemetry.io/inject-java: "true"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Fix&lt;br&gt;
Almost instantly, traces populated the SigNoz dashboard.&lt;br&gt;
The trace proved the database was responding in single-digit milliseconds. The delay was happening before the database call. By clicking from the trace straight into the correlated JVM metrics, I saw application threads were deadlocking waiting for asynchronous external responses. We patched the thread pool logic, and response times dropped back to milliseconds.&lt;br&gt;
Takeaway&lt;br&gt;
Auto-instrumentation via Kubernetes annotations is a superpower. Standardizing on OpenTelemetry saves weeks of engineering time and stops vendor lock-in. Don't guess why your app is slow—trace it.&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>signoz</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
