<?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: Bernard K</title>
    <description>The latest articles on DEV Community by Bernard K (@bernardkibathi).</description>
    <link>https://dev.to/bernardkibathi</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%2F940836%2F23301079-647b-4953-991f-bc09d5c6498f.jpg</url>
      <title>DEV Community: Bernard K</title>
      <link>https://dev.to/bernardkibathi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bernardkibathi"/>
    <language>en</language>
    <item>
      <title>Why Your IoT Data Falls Short Before Reaching the ML Model</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 17 Sep 2026 14:02:15 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/why-your-iot-data-falls-short-before-reaching-the-ml-model-1d63</link>
      <guid>https://dev.to/bernardkibathi/why-your-iot-data-falls-short-before-reaching-the-ml-model-1d63</guid>
      <description>&lt;p&gt;When I first jumped into IoT, I imagined a world where data flowed perfectly from my sensors to machine learning models. But if there's one thing managing over 2,500 devices in Kenya has taught me, it's that the journey of IoT data is more like a bumpy dirt road than a smooth, paved highway. Over time, I've seen that data quality often goes wrong long before it ever reaches your ML model. Let me take you through what I found and why this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reality check: sensors are far from perfect
&lt;/h2&gt;

&lt;p&gt;The first eye-opener came when we deployed an IoT solution in a rural area with budget constraints. Our temperature sensors, sourced at a low cost, started reporting odd values. It was hot, but not "boil your kettle on the ground" hot.&lt;/p&gt;

&lt;p&gt;Turns out, cheap sensors often come with compromises in precision and reliability. They can result in noisy data that's more misleading than useful. In one specific case, I noticed that replacing a faulty batch reduced anomalies by over 30%, but at a higher upfront cost. Quality often correlates with price, a trade-off you have to balance when working in emerging markets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data transmission: lost in translation
&lt;/h2&gt;

&lt;p&gt;Connectivity in Kenya, especially in remote areas, is flaky. I learned this when our moisture sensors intermittently dropped out, causing gaps in data that threw off our models. A 4G modem seemed like the perfect solution until I realized the coverage was spotty. We spent a lot of time figuring out how to buffer data locally and retry sending it once connectivity resumed.&lt;/p&gt;

&lt;p&gt;Introducing a 5-second retry delay helped reduce our data loss by about 80%. Yet, even with such a fix, the intermittent connectivity means our data pipeline isn't always up-to-date, directly affecting model performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serialization and protocols aren't just fancy words
&lt;/h2&gt;

&lt;p&gt;If you've worked with IoT, you know about protocols like MQTT, CoAP, and HTTP. I started with HTTP because, honestly, it’s what I knew best. But the overhead turned my small data packets into chunky logs. It slowed down the whole process, making a snappy data stream into a sluggish trickle.&lt;/p&gt;

&lt;p&gt;Switching to MQTT not only reduced our packet size but also improved delivery reliability. For example, sensor data that took over a minute to transmit via HTTP now zipped through in less than 30 seconds. The immediate impact was a smoother flow to our ML models, although it came with a steeper learning curve for my team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data storage: cleaner isn’t always cheaper
&lt;/h2&gt;

&lt;p&gt;Storing IoT data seems straightforward until you realize how quickly it adds up. Our humidity sensors log data every 10 seconds across several hundred nodes. Over a year, you're swimming in terabytes of unfiltered numbers.&lt;/p&gt;

&lt;p&gt;Enter data lakes and cloud storage. They promise scalability, but I soon found out that without proper management, you're just paying more to store garbage. We automated a data validation pipeline using n8n, filtering nonsense from useful insights upfront. We shaved off about 20% from our storage costs without losing operational data fidelity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Oddball outliers and calibration conflicts
&lt;/h2&gt;

&lt;p&gt;Outliers are often a telltale sign that something's off. What I saw over time is that improper calibration can masquerade these outliers as legitimate data points. In one deployment involving air quality sensors, we miscalibrated just one location, making the entire dataset unreliable.&lt;/p&gt;

&lt;p&gt;Regular recalibration intervals and cross-referencing with a handful of "gold standard" sensors became our go-to approach. It wasn't foolproof, but it made our datasets significantly more trustworthy. After adopting this strategy, anomaly detections that used to take hours were cut down to minutes, improving our response times dramatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  From sensor to model: data preprocessing is necessary
&lt;/h2&gt;

&lt;p&gt;Once you've battled through garbage sensors, patchy connectivity, bloated protocols, and data storage costs, you arrive at the preprocessing stage. This is critical because by this point, you're working with what you've got, good or bad.&lt;/p&gt;

&lt;p&gt;We built a custom pipeline using Python and libraries like Pandas to clean, normalize, and transform data before it touched our ML models. This preprocessing reduced noise and increased predictive accuracy by over 15% in some of our experiments. Not a trivial accomplishment given the rocky path the data had taken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Data quality in IoT is often considered a "solved" problem, but trust me, it's far from it. Every decision, from selecting sensors and communication protocols to storage solutions and preprocessing schemes, impacts your data quality more than you might think. Sure, you could throw money at the problem to get top-notch equipment and premium services, but if you're working within constraints, especially in environments like Kenya, you need to be smart about it.&lt;/p&gt;

&lt;p&gt;If I could share one takeaway: treat IoT data quality issues as close to the source as you can. Whether you're dealing with an intermittent connection in Nairobi or budget hardware in a rural setup, start there. Your future self, and your ML models, will thank you.&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Choosing the Right LangChain Memory: A Practical Guide for Developers</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 17 Sep 2026 14:02:09 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/choosing-the-right-langchain-memory-a-practical-guide-for-developers-1do7</link>
      <guid>https://dev.to/bernardkibathi/choosing-the-right-langchain-memory-a-practical-guide-for-developers-1do7</guid>
      <description>&lt;p&gt;I recently faced an interesting challenge while managing a fleet of IoT devices in Kenya. Managing over 2,500 devices requires careful data handling to maintain scalability and efficiency. Intermittent connectivity and budget constraints added to the complexity. That's when I started looking into memory strategies in LangChain. Here’s what I learned about their uses and limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context: IoT meets AI automation
&lt;/h2&gt;

&lt;p&gt;My main goal was to create a reliable pipeline using LangChain concepts to analyze real-time telemetry data. These devices send data over MQTT. Ensuring smooth processing under real infrastructure constraints meant selecting the right memory strategy. Spoiler: not all of them fit our needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring LangChain’s memory options
&lt;/h2&gt;

&lt;p&gt;There are a few choices when it comes to memory strategies in LangChain: short-term, long-term, and hybrid options. Each has its place, but they’re not all suitable for IoT environments with unreliable connectivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-term memory: Quick but ephemeral
&lt;/h3&gt;

&lt;p&gt;Short-term memory in LangChain offers fast access and low latency, which seemed appealing given our high-frequency data inputs. However, this approach fell short when connectivity issues arose. We frequently lost data, creating gaps in our telemetry analysis.&lt;/p&gt;

&lt;p&gt;Here's a snippet showing my initial setup:&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;from&lt;/span&gt; &lt;span class="n"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ShortTermMemory&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize short-term memory
&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ShortTermMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Adding IoT data to memory
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&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="c1"&gt;# Retrieve the latest data
&lt;/span&gt;&lt;span class="n"&gt;latest_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For applications needing consistency and reliability, this wasn't enough, especially with a flaky network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long-term memory: Reliable but resource-intensive
&lt;/h3&gt;

&lt;p&gt;Long-term memory strategies offered the reliability we needed. They allow data retention through disconnections, similar to a cloud-based database. However, this required extra storage and processing power, straining our budget hardware.&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;from&lt;/span&gt; &lt;span class="n"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LongTermMemory&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize long-term memory
&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LongTermMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/var/data/telemetry.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Storing persistent data
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this on our typical 2GB RAM devices slowed processing, especially when handling large data packets. But for essential parts of our system, the tradeoff was worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hybrid approach: Tailoring with constraints
&lt;/h2&gt;

&lt;p&gt;Considering the pros and cons of both approaches, I considered combining them. Using a hybrid strategy let me balance speed and reliability. By storing real-time telemetry in short-term memory and periodically syncing essential data to long-term storage, I optimized for both performance and reliability without overspending.&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;from&lt;/span&gt; &lt;span class="n"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ShortTermMemory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LongTermMemory&lt;/span&gt;

&lt;span class="c1"&gt;# Hybrid memory approach
&lt;/span&gt;&lt;span class="n"&gt;short_term_memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ShortTermMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;long_term_memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LongTermMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/var/data/essential.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Store and sync data
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;short_term_memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_critical_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;long_term_memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup worked well in production. The hybrid model reduced latency for non-critical data while ensuring we retained critical bits when connectivity faltered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring and maintenance: Keep the system lean
&lt;/h2&gt;

&lt;p&gt;I found out quickly that even the best-configured system needs monitoring, especially when devices are scattered across areas with spotty coverage. By integrating simple alerts and automated scripts to monitor memory usage and sync tasks, I kept the system responsive and cost-efficient.&lt;/p&gt;

&lt;p&gt;Here's a basic monitoring snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Monitor&lt;/span&gt;

&lt;span class="c1"&gt;# Setup a basic memory monitor
&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;long_term_memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory usage high, considering cleanup.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Check every minute
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;What surprised me most about LangChain's memory strategies was how context-specific their effectiveness is. There's no one-size-fits-all solution, especially with intermittent connectivity and limited hardware resources. &lt;/p&gt;

&lt;p&gt;If you're dealing with similar issues, don't automatically choose the long-term or short-term options. Consider your specific needs. For me, the hybrid approach bridged the gap, maintaining responsiveness while keeping essential data intact. &lt;/p&gt;

&lt;p&gt;Next, I'm looking into integrating anomaly detection into this pipeline. This will likely push these strategies to their limits again, but that’s another challenge for the future.&lt;/p&gt;

</description>
      <category>langchain</category>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>Why My IoT Data Quality Misses the Mark Before ML Processing</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 10 Sep 2026 14:02:37 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/why-my-iot-data-quality-misses-the-mark-before-ml-processing-20k3</link>
      <guid>https://dev.to/bernardkibathi/why-my-iot-data-quality-misses-the-mark-before-ml-processing-20k3</guid>
      <description>&lt;p&gt;When I first started working with IoT data feeds from over 2,500 devices, I thought we were about to significantly improve our predictive models. But, as it turned out, the data often fails before it even gets close to powering any machine learning processes. I noticed this firsthand, juggling between my environment in Kenya,where connectivity can be as erratic as the weather,and the practicalities of working with budget hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensor drift and calibration issues
&lt;/h2&gt;

&lt;p&gt;One of the main problems with IoT data quality is sensor drift. Over time, sensors deviate from their initial calibrated states, leading to inaccurate data. In an ideal lab, constant recalibration is an option, but with devices spread across different terrains in Kenya, recalibrating each one is logistically challenging.&lt;/p&gt;

&lt;p&gt;For example, we had air quality sensors in Nairobi that were reporting wildly fluctuating data. It turned out that the sensors misbehaved due to daily temperature changes. Although we fixed it with calibration updates via OTA firmware pushes, identifying the root cause took weeks. It emphasized the importance of routinely checking calibration across all devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Garbage data and connectivity problems
&lt;/h2&gt;

&lt;p&gt;Working across regions with unreliable connectivity adds more complexity. When devices lose connectivity, they might store data for later transmission or, worse, lose it altogether, creating gaps in datasets. Data sent through unstable connections can also become corrupted, leading to garbage data ending up in storage.&lt;/p&gt;

&lt;p&gt;We implemented a local caching solution, allowing data to buffer until it could reliably send to our main server. Using an n8n automation, any incoming data that didn’t match expected patterns, like corrupted temperature readings of 1000°C, was flagged for manual review. This built-in sanity check reduced data errors by about 30%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema evolution challenges
&lt;/h2&gt;

&lt;p&gt;Another lesson came from schema evolution. When our sensor setups upgraded, the data schema changed slightly,or significantly in some cases. This wasn't an issue until we started feeding data directly into machine learning pipelines.&lt;/p&gt;

&lt;p&gt;Our initial models saw some improvements, then plateaued. Upon inspecting, it became clear that changes,like the addition of a new sensor type,broke our preprocessing steps, resulting in mismatched inputs and outputs. Implementing a flexible schema versioning and validation system was essential. After initial learning pains, we now pass all incoming data through an n8n workflow to ensure it aligns with the latest schema version before reaching any feature extraction jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off with computational overhead
&lt;/h2&gt;

&lt;p&gt;Supporting intermittent connectivity and schema checks means adding a layer of computation. This was especially tough in environments where computational power is limited. I faced this when our setup used Raspberry Pi devices with only 1GB of RAM in some instances.&lt;/p&gt;

&lt;p&gt;Setting up a lightweight edge computing system helped reduce the load by pre-processing data. Balancing this with energy consumption and processing power was tricky. Here, LangChain deployments helped optimize our computational needs. The models run lightweight algorithms on-device to pre-filter data, cutting down unnecessary chatter on our main servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world variability
&lt;/h2&gt;

&lt;p&gt;In emerging markets like Kenya, environmental factors can be challenging. Dust, humidity, and interference from other electronic devices can disrupt IoT devices. Deploying devices across various locations highlighted the substantial differences in environmental impact.&lt;/p&gt;

&lt;p&gt;For example, rain and mud disrupted the soil moisture sensors deployed for agriculture monitoring. From this, we developed shields and enclosures that could withstand significant environmental exposure. This helped maintain hardware performance and prevented the need for costly on-site device swaps and repairs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future focus on fine-tuning
&lt;/h2&gt;

&lt;p&gt;After dealing with these data quality issues, the main takeaway is how essential fine-tuning is,fine-tuning in setup, ongoing monitoring, and post-deployment response. Automation has helped immensely, from n8n's role in ensuring data quality to LangChain's lightweight processing.&lt;/p&gt;

&lt;p&gt;Going forward, I plan to explore more effective solutions for managing data quality right at the source. Edge computing could be beneficial, especially with the potential to integrate more intelligent decision-making capabilities directly into the IoT devices. Creating a mini data correction mechanism on the router to address schema evolution and garbage data might be our next step.&lt;/p&gt;

&lt;p&gt;It's clear that IoT data integrity is less about one-time solutions and more about continuous iteration, adaptation, and localized problem-solving, particularly when working under constraints common in emerging economies.&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Deploying a LangChain App on a $5 VPS: A Step-by-Step Guide</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:02:37 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/deploying-a-langchain-app-on-a-5-vps-a-step-by-step-guide-35d6</link>
      <guid>https://dev.to/bernardkibathi/deploying-a-langchain-app-on-a-5-vps-a-step-by-step-guide-35d6</guid>
      <description>&lt;p&gt;Deploying a LangChain app on a $5/month VPS was an experiment born out of necessity. Working in Kenya, where internet speeds can fluctuate more than you'd like and budgets are tight, creativity becomes essential. Most guides cater to those with access to endless resources, but that's far from reality in my case. Still, I was determined to see if I could make it work without burning cash or sacrificing performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I chose VPS for LangChain
&lt;/h2&gt;

&lt;p&gt;Why go for a VPS? A $5/month VPS from providers like DigitalOcean or Vultr offers a decent playground with 1GB RAM and 25GB SSD storage. It provides better control compared to shared hosting and has enough power for a lightweight application, assuming you're not hosting for a massive audience. Additionally, local hosting options that fit African economies are still developing, so we often rely on these international options.&lt;/p&gt;

&lt;p&gt;Using a VPS allows me to manage configurations precisely, which is essential for optimizing performance in low-bandwidth scenarios. Connectivity can drop just because it rained or a bird sat on a line, so this flexibility is significant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up LangChain on a budget
&lt;/h2&gt;

&lt;p&gt;After deciding on a VPS provider, the challenge was to get the app running efficiently. It wasn't straightforward, but here's my approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Python 3 and VirtualEnv
&lt;/h3&gt;

&lt;p&gt;LangChain is built on Python, so naturally, I started by setting up Python 3. Installing it was straightforward, but it's always a good idea to run apps in a virtual environment to avoid conflicts and manage dependencies separately.&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;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;python3-pip python3-venv
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv langchain-env
&lt;span class="nb"&gt;source &lt;/span&gt;langchain-env/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup isolates our app and lets us install exactly what we need without affecting the system Python packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing LangChain
&lt;/h3&gt;

&lt;p&gt;With the virtual environment ready, I installed LangChain along with FastAPI to build a simple interface.&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;langchain fastapi uvicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastAPI is ideal for budget-conscious developers because it’s fast and asynchronous without unnecessary overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing a basic LangChain workflow
&lt;/h3&gt;

&lt;p&gt;For the application, I wanted something practical, like handling real-world data challenges. LangChain's ability to connect with different language models was perfect for my NLP-based app, which focused on translating local dialects that most AI language models overlook.&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;from&lt;/span&gt; &lt;span class="n"&gt;langchain&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LangChainApp&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;langchain_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LangChainApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/translate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_language&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;langchain_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_language&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translated_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This basic app takes text and a target language as input, returning the translated text. It's simple enough to keep resource usage low but functional to demonstrate LangChain's capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling real-world constraints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Memory management
&lt;/h3&gt;

&lt;p&gt;With only 1GB RAM, careful management is crucial. During initial tests, multiple simultaneous requests could slow things down. I used a swap file to mitigate this. It's not the most efficient, but necessary in low-memory environments.&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;sudo &lt;/span&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 1G /swapfile
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helped prevent server crashes during peak requests or while running updates during midday slumps when connectivity was poor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intermittent connectivity workaround
&lt;/h3&gt;

&lt;p&gt;In areas with sporadic internet, maintaining resilience in the server is key. Implementing retries for failed API calls was necessary. I used FastAPI's background tasks to set up a retry mechanism, preventing failed translations from blocking the workflow without manual intervention.&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attempt_translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;langchain_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Translation failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, retrying...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;attempt_translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/translate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate_with_retries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_language&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;background_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt_translation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_language&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Translation task added&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;p&gt;This approach helped manage incoming requests during downtimes, allowing the VPS to deal with unstable connections smartly. It cut task failures by nearly 40%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict and future steps
&lt;/h2&gt;

&lt;p&gt;Running LangChain on a $5 VPS isn't perfect, but it's feasible. The app performed well for small-scale use, with latency averaging around 200ms in optimal conditions. This setup especially benefits developers in emerging markets as a testing ground for cost-constrained apps.&lt;/p&gt;

&lt;p&gt;Next, I'll explore integrating additional local language datasets into LangChain to enhance its utility in underrepresented languages. This journey highlights the importance of adapting tools to our specific realities rather than waiting for tailored solutions.&lt;/p&gt;

</description>
      <category>langchain</category>
      <category>ai</category>
      <category>python</category>
      <category>deployment</category>
    </item>
    <item>
      <title>RAG vs Fine-Tuning: What Actually Solves Your Problem?</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:02:26 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/rag-vs-fine-tuning-what-actually-solves-your-problem-20d6</link>
      <guid>https://dev.to/bernardkibathi/rag-vs-fine-tuning-what-actually-solves-your-problem-20d6</guid>
      <description>&lt;p&gt;When I first got involved in improving AI models for our IoT devices spread across different parts of Kenya, I quickly realized that our resources were limited and connectivity was unpredictable. With over 2,500 IoT devices working in this setup, I needed my AI models to be efficient, fast, and adaptable to our challenging network conditions and tight budgets. The challenge of choosing between Retrieval-Augmented Generation (RAG) techniques and fine-tuning models was not just academic,it was a necessity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding needs
&lt;/h2&gt;

&lt;p&gt;Initially, I chose fine-tuning because it was emphasized by many as the best way to make your model perfect for your data. It seemed like the ideal solution, but I hit a wall. Managing these operations on my 2GB RAM VPS was hard enough, and fine-tuning added more computational cost and time than I could handle. Processing time increased from an average of 15 minutes to nearly an hour. In an environment where power and network availability are unreliable, this was unsustainable.&lt;/p&gt;

&lt;p&gt;On the other hand, RAG offered some hope. By using existing models with document retrieval, it worked better with the 2,500+ devices continuously providing input to our pipeline. A retrieval model allowed me to update only the necessary data points instead of the entire model, minimizing downtime. This reduced processing time to about 20 minutes, a significant improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making RAG work in practice
&lt;/h2&gt;

&lt;p&gt;RAG techniques allowed me to combine a pre-trained language model with specific data retrieval. This meant that instead of the model needing all its knowledge upfront, it could request specific information as needed. This approach suited our budget setup, running on Raspberry Pi units and similar technology.&lt;/p&gt;

&lt;p&gt;Implementing RAG came with its own challenges, though, particularly with latency. If the retrieval component fails to cache efficiently or the connectivity drops, delays occur. Occasionally, a 2-second response time stretched out to 30 seconds due to network issues. Here's a straightforward example of how I set up my RAG with LangChain:&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;from&lt;/span&gt; &lt;span class="n"&gt;langchain.docstore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimpleDocStore&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;

&lt;span class="c1"&gt;# Create a document store
&lt;/span&gt;&lt;span class="n"&gt;doc_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleDocStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;doc_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_documents&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kenya experiences diverse weather conditions affecting sensor readings.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Intermittent connectivity poses challenges for consistent data transmission.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Set up RetrievalQA
&lt;/span&gt;&lt;span class="n"&gt;qa_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetrievalQA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docstore&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;doc_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Querying the system
&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;qa_chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How does the weather affect sensor readings?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&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;# Outputs: "Kenya experiences diverse weather conditions affecting sensor readings."
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup allowed me to answer queries efficiently without overloading the system, provided that connectivity remained stable enough to access the document store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fine-tuning isn't completely off the table
&lt;/h2&gt;

&lt;p&gt;Despite the flexibility and adaptability of RAG, I haven't entirely abandoned fine-tuning. Certain applications, like predictive maintenance models, still benefit from being fine-tuned. These models need to detect specific patterns that are unique to our environment. I found that fine-tuning a smaller subset of the model and deploying it locally in areas with potential network isolation was effective.&lt;/p&gt;

&lt;p&gt;Though fine-tuning increased the initial setup time, it reduced latency in response times. By segmenting and fine-tuning only essential parts of the model, I managed to cut deployment configurations by about 35%. The trick was identifying which parts of the model required frequent updates and which parts could remain unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons from the field
&lt;/h2&gt;

&lt;p&gt;Balancing RAG and fine-tuning taught me valuable lessons in resource management. Here's what I learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Evaluate resource capabilities: Fine-tuning can bottleneck on low resources and is best suited for static, locally important models.&lt;/li&gt;
&lt;li&gt;Understand network realities: RAG requires efficient caching systems to deal with connectivity interruptions, otherwise response times can deteriorate.&lt;/li&gt;
&lt;li&gt;Consider hybrid systems: Use RAG for handling dynamic, variable data needs and fine-tuning for static, crucial analysis, especially when quick responses affect operations.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;I'm not done yet. My plan is to adopt lighter but still reliable caching strategies with RAG to reduce delays during network disruptions. Fine-tuning will continue to develop as we figure out which models benefit the most.&lt;/p&gt;

&lt;p&gt;For anyone facing similar challenges in IoT or resource-constrained environments, it's important to stay adaptable. The field of AI offers a range of approaches. Ultimately, what matters most is understanding your situation and combining methods that typically seem at odds. That's where real innovation, and sometimes sanity, lies.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>langchain</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Getting Started with AI Agents in n8n: A Non-Engineer's Guide</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 09 Jul 2026 14:02:45 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/getting-started-with-ai-agents-in-n8n-a-non-engineers-guide-3g5b</link>
      <guid>https://dev.to/bernardkibathi/getting-started-with-ai-agents-in-n8n-a-non-engineers-guide-3g5b</guid>
      <description>&lt;p&gt;When I first started using n8n to build AI agents, I was dealing with a specific problem: how to automate and improve processes for non-developers who needed to act on IoT data. The main challenge was that most people I worked with just wanted things to "work" without delving into the code. After building over 2,500 IoT devices that report everything from temperature to air quality, it was clear that any additional insight from AI needed to be integrated into workflows everyone could understand and use.&lt;/p&gt;

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

&lt;p&gt;Before settling on n8n, I experimented with other automation tools. They either required too much code or weren’t flexible enough to handle the type of data we were dealing with in Kenya, where the internet can be spotty. So why n8n? It's open-source, allows for visual workflows, and most importantly, it's adaptable to handle unreliable connectivity and budget hardware: a reality here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting AI Agents to Do the Heavy Lifting
&lt;/h2&gt;

&lt;p&gt;Here's why AI agents can transform n8n workflows. Imagine you've got sensor data streaming around the clock. Manually reviewing it,especially with intermittent internet,is not practical. Who has time for that, right? That's where AI comes in. You can process and make sense of data as it arrives: flag anomalies, predict trends, and even automate responses,without writing code.&lt;/p&gt;

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

&lt;p&gt;I remember the first workflow I built to automate email alerts. It was basic but effective. Let's break it down with an example of anomaly detection using sensor data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Workflow Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;: Start with a Webhook to catch incoming data from our IoT devices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Formatting&lt;/strong&gt;: Use a Function node to clean and prepare the data. It's essential to handle missing values or inconsistencies, which are expected with irregular networks. Here's a simplified version:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleanedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;// Use current time if timestamp is missing&lt;/span&gt;
       &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;// Default to 0 if value is missing&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="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cleanedData&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;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI Analysis&lt;/strong&gt;: Use a local AI model or call an API for anomaly detection. Initially, I opted for external APIs due to budget hardware limitations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alert System&lt;/strong&gt;: If an anomaly is detected, use an Email node to notify stakeholders immediately.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Real Savings
&lt;/h3&gt;

&lt;p&gt;Deploying this initial version cut processing time from manually checking and acting on these anomalies,from hours down to minutes. Literally, from "Oh no, why is this thing overheating?" to "Alert: Possible overheating detected at [timestamp]" in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Tradeoffs
&lt;/h2&gt;

&lt;p&gt;I hit a snag when relying heavily on external AI services. Costs for API calls can add up quickly, especially when analyzing data every few minutes across all devices.&lt;/p&gt;

&lt;p&gt;So, what's the workaround if the budget is tight? Preprocess data locally using edge computing concepts. Offload only the essential data to your AI service. I wrote tiny scripts that run on-device to decide if we should even bother sending the data based on historical trends captured locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and Realities
&lt;/h2&gt;

&lt;p&gt;Intermittent connectivity was the major hurdle. When your internet dropout can last an hour and your "cloud" is really just one local server with stock parts, you have to rethink strategies.&lt;/p&gt;

&lt;p&gt;Buffering data locally and pushing it out during network windows saved a lot of hassle. Building in retry logic also meant more data got through in the end, without manual intervention.&lt;/p&gt;

&lt;p&gt;As I refined these workflows, I realized that delaying retries for even just 5 seconds fixed about 80% of dropped connections,a simple tweak with a huge impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest Thoughts
&lt;/h2&gt;

&lt;p&gt;n8n isn’t perfect. It’s not as sleek as some premium offerings, and sometimes nodes don't work exactly as expected, requiring a bit of hacking. But for the flexibility, community support, and open customization it offers, it's been incredibly useful in bridging tech gaps.&lt;/p&gt;

&lt;p&gt;If you’re working under the constraints of budget, limited tech infrastructure, and variable connectivity like we are here in Kenya, n8n could be worth a shot.&lt;/p&gt;

&lt;p&gt;Next step? Scaling up! I'm exploring how we can integrate more refined AI capabilities directly onto devices, moving beyond mere alerting to full-on automated decision-making. It's a frontier worth exploring, and we're just scratching the surface of what's truly possible.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>n8n vs Make vs Zapier: Why I Made the Switch and What I Learned</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 23 Jun 2026 12:54:17 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/n8n-vs-make-vs-zapier-why-i-made-the-switch-and-what-i-learned-3686</link>
      <guid>https://dev.to/bernardkibathi/n8n-vs-make-vs-zapier-why-i-made-the-switch-and-what-i-learned-3686</guid>
      <description>&lt;p&gt;I switched to n8n after grappling with some limitations in Zapier and Make (formerly Integromat), and it has worked out well for me. Working with IoT in Kenya presents unique challenges, from budget-friendly hardware to unreliable internet connections. Each automation tool has its specific use, but here's why n8n has become my main tool for managing over 2,500 IoT devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost factor
&lt;/h2&gt;

&lt;p&gt;Running over 2,500 IoT devices is expensive, and every dollar counts when budgets are tight. Zapier's pricing can become very high with thousands of monthly operations. Make offers more generous operation limits, but n8n's self-hosting option was a major improvement for me. It allowed me to control costs tightly while scaling infrastructure as needed. Running n8n on a local server saved us about $200 a month compared to the Zapier plan we would have needed. That might sound small, but it’s significant for a startup in Nairobi. Plus, self-hosting meant I wasn't reliant on consistent internet connectivity to a third-party server, which is essential when your connection is spotty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization freedom
&lt;/h2&gt;

&lt;p&gt;Managing a fleet of IoT devices often requires tailored solutions. I realized this after finding Zapier's structure too rigid. While Zapier has a large library of integrations, it didn’t offer the flexibility I needed. n8n allowed me to script custom nodes into my automation workflows using JavaScript. Once, we needed a custom node to process sensor data differently based on dynamic inputs. I managed it in a weekend with n8n. Here's a small snippet of how I handled some JSON data from my devices:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sensorData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&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;data&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;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;humidity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sensorData&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;temperature&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;webhookUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://my-webhook-url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Temperature Alert!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;temperature&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;Having this control without needing additional tools or elaborate workarounds was refreshing. n8n is like having LEGO blocks for automation, letting you build anything without limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intermittent connectivity
&lt;/h2&gt;

&lt;p&gt;Those working in areas with stable, high-speed internet often overlook the importance of resilience in automation. In Kenya, internet hiccups are frequent, which shaped how I evaluated these platforms. Zapier and Make rely heavily on consistent internet access. Their cloud-based nature can lead to frequent interruptions if the connection drops. With n8n, hosting it on a local server reduced our dependency on external network stability. This setup decreased failures in data collection and processing by about 25%, a vital improvement for reliable operations.&lt;/p&gt;

&lt;p&gt;For example, we implemented a retry mechanism for critical API calls, and it significantly improved data integrity. Check out this simple retry logic we set up:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;attempts&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="nx"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;httpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://some-api.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Max retries reached&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example shows how n8n allows for more customized error handling, making it essential for operations in environments like mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning curve and community support
&lt;/h2&gt;

&lt;p&gt;n8n has a steeper learning curve than the other two, and I spent a few solid days reading docs and searching online. However, the n8n community is great. It reminded me of my early days tinkering with open-source hardware projects, finding a collective eager to share solutions and ideas. The transition was initially challenging, but I haven’t looked back. The hands-on learning was refreshing, and it ultimately gave me a deeper understanding of automation flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world impact
&lt;/h2&gt;

&lt;p&gt;Using n8n in our workflow changed our operations significantly. We saw a 30% increase in processing efficiency over the previously used tools, not just theoretically but in how quickly and reliably our automated alerts and processes ran.&lt;/p&gt;

&lt;h3&gt;
  
  
  The next step
&lt;/h3&gt;

&lt;p&gt;Looking ahead, I’m excited to explore advanced n8n features like custom user interfaces and integrating other open-source tools. I'm even considering contributing to n8n's codebase myself. For developers managing IoT environments, especially in areas with infrastructure challenges, n8n is worth trying. The blend of cost-effectiveness, flexibility, and adaptability to low-quality connection scenarios makes it a reliable partner. Real success is about having a tool that works well under the true constraints you face.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Lead Scoring Pipeline in n8n with GPT-4o-mini: A Step-by-Step Guide</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 30 Apr 2026 14:02:31 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/building-a-lead-scoring-pipeline-in-n8n-with-gpt-4o-mini-a-step-by-step-guide-3ein</link>
      <guid>https://dev.to/bernardkibathi/building-a-lead-scoring-pipeline-in-n8n-with-gpt-4o-mini-a-step-by-step-guide-3ein</guid>
      <description>&lt;p&gt;I've been working with IoT devices in environments with limited connectivity and budget constraints here in Kenya. With over 2,500 devices operational, optimizing processes is essential for me. Recently, I embarked on a project to build a lead scoring pipeline using n8n with GPT-4o-mini. Given my experience with tight budgets and intermittent connections, finding solutions that perform reliably in these conditions is always rewarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation: Why n8n and GPT-4o-mini?
&lt;/h2&gt;

&lt;p&gt;In searching for an efficient lead scoring solution, I chose n8n and GPT-4o-mini because they are open source and flexible. n8n is a node-based automation tool, which allowed me to set up custom workflows without excessive costs. Pairing it with GPT-4o-mini, a lightweight language processing model, was a great fit for my infrastructure constraints.&lt;/p&gt;

&lt;p&gt;I previously wrote about setting up lead scoring using these tools, but this time I aimed to create something more reliable and share how it performed in our local setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the environment
&lt;/h2&gt;

&lt;p&gt;I started by deploying n8n on a virtual server with just 2GB of RAM. Setting up was straightforward, thanks to n8n's Docker support. Here's how I did it:&lt;br&gt;
&lt;/p&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;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; n8n &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5678:5678 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ~/.n8n:/home/node/.n8n &lt;span class="se"&gt;\&lt;/span&gt;
  n8nio/n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once n8n was up and running, I integrated GPT-4o-mini into my workflow. The framework offers a manageable model size that runs smoothly on my server, even during peak loads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the lead scoring workflow
&lt;/h2&gt;

&lt;p&gt;I developed a workflow that collects customer interaction data, processes it through GPT-4o-mini, and outputs a lead score. This setup involved nodes for data storage (I used MySQL for its reliability), data processing, and score calculation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data ingestion
&lt;/h3&gt;

&lt;p&gt;Data came in from various sources. I configured n8n to gather data from multiple channels like email and web forms. Each data source funneled into a database node in n8n:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nodes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parameters&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;operation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;executeQuery&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM leads;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fetch Leads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;n8n-nodes-base.mySql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;typeVersion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;This setup allowed me to handle a consistent data flow of about 500 new entries daily: it's not a heavy load but enough to test reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processing with GPT-4o-mini
&lt;/h3&gt;

&lt;p&gt;The next step was to evaluate qualitative aspects like customer sentiment through GPT-4o-mini:&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;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GPT4OMini&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the model
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GPT4OMini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Process lead interaction data
&lt;/span&gt;&lt;span class="n"&gt;lead_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_lead_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interaction_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This processed data efficiently without overwhelming my limited server resources. Processing time averaged 10 seconds per lead, which was suitable for my needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling connectivity challenges
&lt;/h2&gt;

&lt;p&gt;Working in regions with unreliable internet is a reality, so I built in safeguards. I implemented retry logic in n8n that reattempts failed operations:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retry&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maxTries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interval&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retry feature fixed about 80% of connectivity issues, ensuring updated scores consistently without requiring manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and costs
&lt;/h2&gt;

&lt;p&gt;The entire setup proved cost-effective. Hosting n8n and GPT-4o-mini on a modest server cost me less than $30 a month. I saved an estimated $200/month compared to outsourcing lead scoring or using a SaaS alternative. My pipeline processed 1,500 records daily, allowing the sales team to focus on high-potential leads, increasing conversion rates by about 15%.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;p&gt;Initially, I tried using a more complex language model, but it was too much for my simple lead scoring needs and frequently crashed under my server constraints. Switching to GPT-4o-mini was a significant improvement. I also encountered issues updating n8n nodes, which sometimes broke the workflow. Locking in stable versions helped maintain consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Deploying a lead scoring pipeline with n8n and GPT-4o-mini turned out to be an efficient way to analyze our customer data within budget constraints. The setup adapted well to the connectivity and processing power challenges typical of my work environment in Kenya. I'm looking into more advanced integrations, such as real-time scoring updates and more detailed sentiment analyses to further improve lead quality insights.&lt;/p&gt;

&lt;p&gt;If you're working in similar conditions with budget hardware and unreliable networks, this setup could provide a reliable starting point without needing high-end resources.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Why IoT Data Stumbles Before Fueling Your ML Models</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 28 Apr 2026 14:03:12 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/why-iot-data-stumbles-before-fueling-your-ml-models-4ae2</link>
      <guid>https://dev.to/bernardkibathi/why-iot-data-stumbles-before-fueling-your-ml-models-4ae2</guid>
      <description>&lt;p&gt;Data quality issues in IoT are often a significant challenge in machine learning projects. This is a reality I've faced while managing over 2,500 active IoT devices in Kenya. It's not just a theoretical problem; it directly impacts how well ML models perform. Here's why IoT data quality can falter before it even reaches your ML models, along with some insights from my experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensor quality variations
&lt;/h2&gt;

&lt;p&gt;A major concern is the difference in sensor quality. Working within budget constraints common in emerging markets often means making tough hardware choices. On occasion, I've used cheaper sensors only to discover higher error margins or, worse, intermittent data issues. For instance, a temperature sensor might report values that vary by as much as 5 degrees Celsius randomly, skewing datasets.&lt;/p&gt;

&lt;p&gt;The takeaway is clear: if you're using budget-friendly hardware, include this in your data collection strategy. Adding layers for data calibration and validation can help address these discrepancies. I've used straightforward statistical validations to flag unusual readings. For example, if a sensor reports a temperature that exceeds expected seasonal ranges, I log it for manual review instead of sending it directly to models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connectivity issues
&lt;/h2&gt;

&lt;p&gt;Network instability is common here, and it's a major cause of data quality problems. In Nairobi, for example, network outages occur more often than I'd prefer. During these periods, devices might either stop recording data if they lack local storage or, worse, report corrupt packets due to mid-transmission cut-offs.&lt;/p&gt;

&lt;p&gt;A practical solution that’s worked for me is using MQTT with persistent session support. This setup lets devices queue messages when the connection drops and push them once it's restored. This approach has reduced data loss by at least 60%. Additionally, building in a local buffer for temporary data storage is invaluable during critical connectivity lapses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing data packet size
&lt;/h2&gt;

&lt;p&gt;Sending detailed telemetry over an unreliable 2G network is problematic. Initial attempts saw packets consistently lost during transmission. Real progress was made by breaking telemetry down into smaller, prioritized packets. Sending crucial metrics first ensured vital insights got through even if secondary data lagged.&lt;/p&gt;

&lt;p&gt;Another tactic is compressing data for transport. Tools like Protobuf are effective for compacting data without sacrificing content quality. This alone reduced our payload sizes by over 40%, leading to steadier data delivery across unreliable networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time synchronization challenges
&lt;/h2&gt;

&lt;p&gt;Ideally, time synchronization should be seamless, but that's not always the case. Many devices lose sync due to connectivity issues, resulting in logs with incorrect timestamps. This can mislead models when cross-referencing datasets.&lt;/p&gt;

&lt;p&gt;I've tackled this with a dual-sync method: devices sync with a local server periodically, and when the network is unreliable, they rely on an internal clock adjusted manually during post-processing. While not perfect, this has greatly enhanced the accuracy of our time-stamped data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Addressing software bugs
&lt;/h2&gt;

&lt;p&gt;Software bugs are more than just a hassle,they can compromise data integrity. I learned this firsthand when an overnight update to our edge computing routines halted our data pipeline due to a memory leak. Implementing rollback capabilities and automated integrity checks have protected us from similar issues since.&lt;/p&gt;

&lt;p&gt;Regular code audits and simulations on sample data before deployment are crucial. These steps have identified numerous minor and major issues that could have otherwise diminished our data's quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;Data quality extends beyond just cleaner data; it's also about strategic resource use. In my experience, IoT projects with successful ML outcomes begin by addressing data quality right from where the sensors are located. Understanding the local context, such as Kenya's connectivity challenges, and designing solutions for those specific conditions is key.&lt;/p&gt;

&lt;p&gt;Looking ahead, I'm exploring integrating AI-driven anomaly detection directly on IoT devices. By using lightweight models capable of running on edge devices, we can potentially flag data inconsistencies in real-time before they spread. It's early days, but real-time data validation could significantly enhance environments like ours.&lt;/p&gt;

&lt;p&gt;Ultimately, when dealing with IoT and ML in real-world situations, perfection is unattainable. Instead, it's about continuous improvement and building processes that evolve with your devices and datasets. This journey is challenging, but each lesson learned brings us closer to extracting truly useful insights from our telemetry.&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How I Built a Document Q&amp;A Bot Using LangChain, FAISS, and Docker</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:02:47 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/how-i-built-a-document-qa-bot-using-langchain-faiss-and-docker-19he</link>
      <guid>https://dev.to/bernardkibathi/how-i-built-a-document-qa-bot-using-langchain-faiss-and-docker-19he</guid>
      <description>&lt;p&gt;Setting up a Document Q&amp;amp;A Bot using LangChain, FAISS, and Docker has been quite the expedition into aligning advanced technology with practical constraints. Being based in Kenya with its infrastructure quirks and tight budgets, I needed something effective yet lightweight enough for environments with intermittent connectivity. Here's how I went about it and what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Chose LangChain, FAISS, and Docker
&lt;/h2&gt;

&lt;p&gt;Having worked with LangChain on previous AI automation projects, I knew it could process natural language effectively. The challenge was integrating it with a reliable search component,FAISS (Facebook AI Similarity Search),to manage document indexing and retrieval efficiently. Docker was the logical choice for maintaining environment consistency across different machines, especially when using budget hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Core: LangChain and FAISS
&lt;/h2&gt;

&lt;p&gt;Integrating LangChain with FAISS allowed me to create a document Q&amp;amp;A bot that searches through large amounts of text to provide relevant answers. Here's a breakdown of how I implemented it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up LangChain
&lt;/h3&gt;

&lt;p&gt;Start by setting up LangChain. If you're unfamiliar with this framework, it simplifies the creation of pipeline structures for text processing. Here's a snippet to show how I initialized it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize your langchain pipeline
&lt;/span&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# Here you would define steps like data processing, machine learning, etc.
&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This straightforward setup prepares for processing natural language queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing FAISS for Search
&lt;/h3&gt;

&lt;p&gt;FAISS was transformative for the search functionality. It's fast and handles high-dimensional data well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# Create data to index
&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;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create an index
&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexFlatL2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Add data to the index
&lt;/span&gt;&lt;span class="n"&gt;index&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# To search for the nearest neighbor
&lt;/span&gt;&lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: array([[0]])
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I found FAISS particularly efficient when dealing with large datasets, which I anticipate as the data grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging with Docker
&lt;/h2&gt;

&lt;p&gt;Using Docker helped manage dependencies and environment setups, crucial when deploying on systems with different specs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile for Environment Consistency
&lt;/h3&gt;

&lt;p&gt;Here's a snippet of the Dockerfile that I used:&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="c"&gt;# Use an official Python runtime as a parent image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.9&lt;/span&gt;

&lt;span class="c"&gt;# Set the working directory in the container&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Copy the current directory contents into the container at /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Install any needed packages specified in requirements.txt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Run langchain script when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "langchain_script.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup prevented dependency issues on different machines and made deploying updates consistently easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges of Connectivity and Budget Constraints
&lt;/h2&gt;

&lt;p&gt;In a region with spotty internet, building a system that handled offline queries was essential. I implemented local caching of common queries and some preprocessing techniques to ensure the app didn't rely on constant internet access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: What Worked and What Didn't
&lt;/h2&gt;

&lt;p&gt;Deploying this setup reduced our search query time from around 2 seconds to under 200 milliseconds. But there were challenges. Budget hardware meant I had to limit the number of threads FAISS could use to avoid maxing out CPU resources.&lt;/p&gt;

&lt;p&gt;Offline capabilities were mixed. Local caching worked for common phrases, but less frequent queries still struggled without internet access. This balance is something I continually tweak.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm exploring ways to include more efficient data preprocessing steps into the LangChain pipeline to further reduce internet dependency. Additionally, I'm optimizing FAISS to handle larger datasets on limited hardware.&lt;/p&gt;

&lt;p&gt;LangChain, FAISS, and Docker together brought significant improvements in managing large-scale document Q&amp;amp;A tasks despite constraints in emerging markets. It's a process of constant iteration and adaptation to our environment's realities.&lt;/p&gt;

</description>
      <category>langchain</category>
      <category>ai</category>
      <category>python</category>
      <category>docker</category>
    </item>
    <item>
      <title>RAG vs Fine-Tuning: What Really Solved My AI Challenges</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 14 Apr 2026 14:03:11 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/rag-vs-fine-tuning-what-really-solved-my-ai-challenges-170g</link>
      <guid>https://dev.to/bernardkibathi/rag-vs-fine-tuning-what-really-solved-my-ai-challenges-170g</guid>
      <description>&lt;p&gt;I recently grappled with the choice between Retrieval-Augmented Generation (RAG) and fine-tuning a language model. The project was simple: integrate AI that's reliably intelligent on a budget, across over 2,500 IoT devices distributed in areas where internet connectivity is as steady as a shaky table. My mission was to enable these devices to answer user questions about local climate data,a feature that needed to be useful even when connections were unstable.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG: A smart choice
&lt;/h2&gt;

&lt;p&gt;RAG turned out to be a lifesaver in my scenario. Its appeal lay in working well with limited resources while maintaining performance. For those unfamiliar, RAG involves pulling relevant documents from a dataset and generating a response based on that retrieved information. Think of it like a librarian who pulls the right book off the shelf before you even finish asking your question.&lt;/p&gt;

&lt;p&gt;Why RAG? Maintaining a large language model locally on budget IoT hardware felt impractical. These devices don't have the processing power or memory for such a task. Streaming a lean model and outsourcing the heavy-lifting to RAG seemed smart and efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I implemented RAG
&lt;/h3&gt;

&lt;p&gt;I used Haystack, a Python framework that integrates well with RAG. The setup was surprisingly straightforward. Here's a simplified version of the code I used:&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;from&lt;/span&gt; &lt;span class="n"&gt;haystack.document_stores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InMemoryDocumentStore&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.nodes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DensePassageRetriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FARMReader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.pipelines&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ExtractiveQAPipeline&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize document store
&lt;/span&gt;&lt;span class="n"&gt;document_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InMemoryDocumentStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Set up retriever and reader
&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DensePassageRetriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;document_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FARMReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name_or_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepset/roberta-base-squad2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Pipeline for QA
&lt;/span&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExtractiveQAPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;document_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&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;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sample output
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the local climate today?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The climate is sunny with high temperatures.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answers&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Expected: "sunny with high temperatures"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was consistent performance despite shaky connectivity. With RAG, not every query required a live internet connection, which drastically reduced latency issues and cut API costs. In numbers, I saw a 50% reduction in unnecessary internet fetches,a big win for us working on a tight budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-tuning: an ambitious endeavor
&lt;/h2&gt;

&lt;p&gt;Now, fine-tuning has its appeal. You can tailor a language model to your specific dataset. Sounds great, right? Unfortunately, it's a costly approach if each of your IoT devices has the computational power of a basic calculator.&lt;/p&gt;

&lt;p&gt;For the same task, fine-tuning a model was like sending these devices to space without oxygen. Fine-tuning is ideal when constant connectivity is guaranteed or when working with larger cloud setups.&lt;/p&gt;

&lt;h3&gt;
  
  
  My attempt with fine-tuning
&lt;/h3&gt;

&lt;p&gt;I tried using BERT, a popular choice known for its strong context understanding. With the dataset in hand, I attempted fine-tuning on a pre-trained model using Transformers:&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;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BertTokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BertForQuestionAnswering&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Trainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TrainingArguments&lt;/span&gt;

&lt;span class="c1"&gt;# Tokenizer and model initialization
&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BertTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bert-base-uncased&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BertForQuestionAnswering&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bert-base-uncased&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A toy dataset split
&lt;/span&gt;&lt;span class="n"&gt;train_encodings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weather?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;truncation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;train_dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;train_encodings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start_positions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;end_positions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;

&lt;span class="c1"&gt;# Trainer setup
&lt;/span&gt;&lt;span class="n"&gt;training_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrainingArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;per_device_train_batch_size&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;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Trainer&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;model&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;training_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Mock training
&lt;/span&gt;&lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this on a more robust setup was insightful, but trying to execute similar fine-tuned models on-field failed badly. Any connectivity hiccup meant retrieving data from the cloud sometimes took longer than the questions themselves. Not to mention, the cost wasn't cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision
&lt;/h2&gt;

&lt;p&gt;In a perfect world with limitless resources, fine-tuning would be the dream engine of AI. But here, keeping IoT functional in unreliable network zones with budget limitations paved a path where RAG shone like a beacon.&lt;/p&gt;

&lt;p&gt;For IoT deployments in regions like Kenya, where budget constraints are as ever-present as the sunsets, RAG is a solid solution. If you're dealing with devices with the processing power of an old Nokia but want a system that performs under challenging conditions, RAG is the way to go.&lt;/p&gt;

&lt;p&gt;For now, we're working to optimize RAG's document retrieval efficiency and exploring additional cloud computing solutions for occasional heavy lifting. Tech evolves fast, and staying ahead requires constant adjustment and experimentation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>langchain</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Quick Guide: Connecting n8n to Any REST API in 10 Minutes</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 09 Apr 2026 14:02:48 +0000</pubDate>
      <link>https://dev.to/bernardkibathi/quick-guide-connecting-n8n-to-any-rest-api-in-10-minutes-9f1</link>
      <guid>https://dev.to/bernardkibathi/quick-guide-connecting-n8n-to-any-rest-api-in-10-minutes-9f1</guid>
      <description>&lt;p&gt;Connecting n8n to a REST API in under 10 minutes might sound ambitious, but it's quite achievable, even for beginners. I stumbled across n8n a few months ago when I was dealing with automating my IoT data reporting. The need for a flexible automation tool was pressing, given the constraints I face working in Kenya: unreliable internet, budget limits, and a need for simplicity. My goal was to automate data fetching from a weather API, and n8n seemed like the perfect tool because of its visual workflow builder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use n8n?
&lt;/h2&gt;

&lt;p&gt;Before getting started, let me explain why n8n caught my attention. It's open-source, meaning no monthly fees, which is a big win when you're watching your budget. Plus, its visual interface means I don't have to hard-code API calls like I used to, saving heaps of time. For someone juggling over 2,500 IoT devices across regions with sporadic connectivity, automating workflows with minimal overhead is incredibly helpful.&lt;/p&gt;

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

&lt;p&gt;First, you need to have n8n running. You can deploy it locally using Docker or any cloud provider. I usually run it on a DigitalOcean droplet with 2GB RAM, costing about $10 a month. This setup has handled my modest workflows without a hitch.&lt;/p&gt;

&lt;p&gt;Once n8n is up and running, you’ll land on a simple canvas ready for your workflow creation. Here's how I connected n8n to a REST API to fetch weather data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the HTTP request node
&lt;/h2&gt;

&lt;p&gt;The HTTP Request node is your gateway to external APIs. Here’s how you set it up to connect to any REST API. I'll use the OpenWeather API as an example.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the HTTP Request Node&lt;/strong&gt;: Drag this node from the left panel to your canvas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the Request&lt;/strong&gt;: Double-click the node and you’ll see options for configuring your HTTP request.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: Select &lt;code&gt;GET&lt;/code&gt; since we are fetching data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt;: Input the API endpoint, like &lt;code&gt;http://api.openweathermap.org/data/2.5/weather?q=Nairobi&amp;amp;appid=YOUR_API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing the API&lt;/strong&gt;: Hit the "Execute Node" button to test your request. Within my setup, this worked smoothly. A successful call displays the response in JSON format, showing temperature, humidity, and more.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Parsing the API response
&lt;/h2&gt;

&lt;p&gt;After you have the raw data, you need to make it usable. Here, the JSON Parse node is quite handy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the JSON Parse Node&lt;/strong&gt;: Connect this to your HTTP Request node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure the Node&lt;/strong&gt;: Simply set the "Fields to Convert" to the field containing your JSON data. In our case, it's the entire API response.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Executing this parses the JSON, making it structured data ready for further processing in your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling connectivity issues
&lt;/h2&gt;

&lt;p&gt;Working in Nairobi, internet connectivity isn't always stable. I handle this by introducing retries and error-checking in n8n.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add a Function Node&lt;/strong&gt;: Create simple JavaScript code to retry the HTTP request if it fails. Here’s a quick snippet:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;retryCount&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Call the HTTP Request Node here&lt;/span&gt;
       &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// set to true if the request succeeds&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="nf"&gt;setTimeout&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="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// wait 5 seconds before retrying&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;success&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&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="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This logic saved me when connecting to APIs from locations with flaky connections, significantly reducing request failure rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation with ease
&lt;/h2&gt;

&lt;p&gt;Once you have your data, you can either store it in a Google Sheet, send it via email, or trigger other IoT devices. n8n offers nodes for just about anything. For reporting my IoT data, I often push this info to a Google Sheet. Simply drop the Google Sheets node into your workflow, link it with your Google Account, and specify the sheet and cells to update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world application
&lt;/h2&gt;

&lt;p&gt;Remarkably, setting up n8n to automate data collection cut manual processing time from 2 hours a day to just 10 minutes of setup and occasional monitoring. In one instance, this saved me roughly $200 a month by eliminating the need for a third-party service to handle HTTP requests and parsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;If you're navigating constraints such as budget hardware or tough internet conditions, n8n can offer a simple, effective solution for automating data flows. While it’s not without its issues (node configuration can occasionally be a bit fiddly), it's dependable enough for most small to medium IoT applications I've managed.&lt;/p&gt;

&lt;p&gt;The next steps? I'm planning to explore triggering device actions based on API responses automatically. Meanwhile, try n8n for your REST API needs. If my experience is anything to go by, you'll appreciate its straightforward approach in a complex world.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
