<?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: Lycore Development</title>
    <description>The latest articles on DEV Community by Lycore Development (@lycore).</description>
    <link>https://dev.to/lycore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3880661%2Fa5bbacf4-a098-49f1-a11d-f9107b2d9d8c.png</url>
      <title>DEV Community: Lycore Development</title>
      <link>https://dev.to/lycore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lycore"/>
    <language>en</language>
    <item>
      <title>React Native's New Architecture in Production: What JSI, Fabric, and TurboModules Actually Change</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Mon, 04 May 2026 05:40:31 +0000</pubDate>
      <link>https://dev.to/lycore/react-natives-new-architecture-in-production-what-jsi-fabric-and-turbomodules-actually-change-1p5j</link>
      <guid>https://dev.to/lycore/react-natives-new-architecture-in-production-what-jsi-fabric-and-turbomodules-actually-change-1p5j</guid>
      <description>&lt;p&gt;React Native's New Architecture — JSI, Fabric, and TurboModules — has been "coming soon" for long enough that some teams wrote it off as vaporware. It shipped. It is now default in new React Native projects. And it meaningfully changes how the framework works at the performance-critical boundaries between JavaScript and native code.&lt;/p&gt;

&lt;p&gt;This post is not a getting-started guide. It is an honest account of what the New Architecture actually changes in production applications — which performance improvements are real, which problems it does not fix, what the migration involves, and what you need to know before enabling it on an existing app.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Old Architecture Got Wrong
&lt;/h2&gt;

&lt;p&gt;To understand what the New Architecture changes, you need to understand what it replaces.&lt;/p&gt;

&lt;p&gt;The Old Architecture communicated between JavaScript and native code through a bridge — an asynchronous, serialisation-based message-passing system. JavaScript could not call native code directly. It sent a serialised message (JSON) to the bridge, the bridge deserialised it, passed it to the native thread, native code executed, serialised a response, and sent it back.&lt;/p&gt;

&lt;p&gt;This created three fundamental problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous communication for synchronous needs.&lt;/strong&gt; Some interactions require synchronous communication between JS and native — reading a layout value to position an animation, for example. With the bridge, this required workarounds that were either slow (async round-trips) or brittle (cached values that could be stale).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serialisation overhead.&lt;/strong&gt; Every interaction between JS and native went through JSON serialisation and deserialisation. For high-frequency interactions — scroll events, gesture callbacks, animation frames — this overhead was measurable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eager initialisation of all native modules.&lt;/strong&gt; The Old Architecture initialised every registered native module at startup, regardless of whether it was used. In large applications with many native modules, this contributed significantly to startup time.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxnofm85tx0qmtxq0laf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxnofm85tx0qmtxq0laf.jpg" alt="Side-by-side architecture diagram comparing React Native old bridge-based architecture with new JSI-based architecture showing the elimination of the asynchronous bridge." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What JSI Actually Does
&lt;/h2&gt;

&lt;p&gt;JSI — the JavaScript Interface — replaces the bridge with direct JavaScript bindings to C++. JavaScript can hold references to native objects and call native methods directly, synchronously, without serialisation.&lt;/p&gt;

&lt;p&gt;The practical effect is that JavaScript can interact with native code with the same directness as calling a JavaScript function. No queuing, no serialisation, no round-trip to the bridge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// JSI binding example — what JSI enables at the C++ layer&lt;/span&gt;
&lt;span class="c1"&gt;// This is simplified from how TurboModules work internally&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NativeStorageModule&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HostObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PropNameID&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// JavaScript calls this directly via JSI&lt;/span&gt;
        &lt;span class="c1"&gt;// No bridge, no serialisation&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"getItem"&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="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;createFromHostFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;name&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;// number of arguments&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&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;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;createFromUtf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unordered_map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;storage_&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;From JavaScript's perspective, calling a JSI-backed function feels identical to calling a regular JavaScript function — because it effectively is. The native implementation runs synchronously on the JavaScript thread via the JSI host object protocol.&lt;/p&gt;




&lt;h2&gt;
  
  
  TurboModules: What Changes for Native Module Development
&lt;/h2&gt;

&lt;p&gt;TurboModules use JSI to provide direct, type-safe access to native code from JavaScript. They replace the old &lt;code&gt;NativeModules&lt;/code&gt; system with two significant improvements: lazy loading and type safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy loading.&lt;/strong&gt; TurboModules are only initialised when first accessed, not at startup. An app that has 30 native modules but uses only 5 of them in any given session initialises only 5. Startup time reflects actual usage rather than the total module count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type safety via Codegen.&lt;/strong&gt; TurboModules are defined in a TypeScript or Flow spec that Codegen uses to generate native interface code automatically. This eliminates the type mismatch bugs that were common in the old system — where you could pass the wrong type from JavaScript to native with no compile-time error, only a runtime crash.&lt;/p&gt;

&lt;p&gt;Here is what a TurboModule spec looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NativeDocumentProcessor.ts — the spec file&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TurboModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TurboModuleRegistry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Spec&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TurboModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;processDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProcessingOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProcessingResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;cancelProcessing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;getVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ProcessingOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;extractTables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;extractImages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ProcessingResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;extractedData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;processingTimeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TurboModuleRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEnforcing&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DocumentProcessor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codegen generates the iOS (Objective-C/Swift) and Android (Java/Kotlin) interface code from this spec. The native implementation provides the actual logic; Codegen provides the glue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fabric: The New Renderer
&lt;/h2&gt;

&lt;p&gt;Fabric is the New Architecture's UI renderer. It replaces the old shadow tree — a background-thread representation of the UI — with a C++ implementation that can run synchronously on the JavaScript thread when needed.&lt;/p&gt;

&lt;p&gt;The most significant practical change for application developers is Concurrent Mode. Fabric enables React's concurrent rendering features in React Native:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suspense for data fetching&lt;/strong&gt; — components can suspend while data loads, with a fallback rendered in their place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;useTransition&lt;/strong&gt; — expensive updates can be deferred without blocking the UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic batching&lt;/strong&gt; — state updates in asynchronous code are batched automatically, reducing re-renders
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Concurrent features now work in React Native with New Architecture&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTransition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Suspense&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DocumentList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startTransition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTransition&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;handleSearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="c1"&gt;// Mark the search result update as a transition&lt;/span&gt;
        &lt;span class="c1"&gt;// UI stays responsive while results load&lt;/span&gt;
        &lt;span class="nf"&gt;startTransition&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="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SearchInput&lt;/span&gt; &lt;span class="nx"&gt;onChangeText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSearch&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Suspense&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DocumentListSkeleton&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DocumentResults&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Suspense&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7idy7b1kp7z65kmxwdxq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7idy7b1kp7z65kmxwdxq.jpg" alt="Performance comparison chart showing four metrics for old versus new React Native architecture including startup time, frame drop rate, native module latency, and memory usage.&amp;lt;br&amp;gt;
" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What the Performance Numbers Actually Look Like
&lt;/h2&gt;

&lt;p&gt;The New Architecture performance improvements are real but context-dependent. Here is what we have measured across our own applications:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup time.&lt;/strong&gt; Improvement is most visible in apps with many native modules. Apps with 20+ native modules see 25–40% startup time reduction from lazy TurboModule initialisation. Apps with few native modules see minimal improvement on this metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scroll and animation performance.&lt;/strong&gt; Frame drop reduction during complex scroll operations is measurable — we have seen drops from ~2% to ~0.3% in list-heavy views. The improvement comes from Fabric's ability to run layout calculations synchronously and its better integration with the native animation system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native module call latency.&lt;/strong&gt; The JSI-based direct call is faster than the bridge for synchronous calls — sub-millisecond versus 5–10ms for bridge serialisation. For async native operations (network calls, disk I/O), the improvement is not visible because the async operation dominates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory usage.&lt;/strong&gt; Modest improvement from lazy module initialisation. We have seen 10–15% reduction in idle memory in apps with large native module counts.&lt;/p&gt;

&lt;p&gt;The headline: the New Architecture delivers real improvements, but the degree of improvement depends heavily on your specific application. Memory-bound apps or apps with complex gesture handling see more dramatic gains than simple content apps.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Migration Reality
&lt;/h2&gt;

&lt;p&gt;Enabling the New Architecture in an existing app is a multi-step process. The biggest variable is third-party library compatibility.&lt;/p&gt;
&lt;h3&gt;
  
  
  Checking Library Compatibility
&lt;/h3&gt;

&lt;p&gt;Before doing anything else, audit your dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx react-native-community/upgrade-support
&lt;span class="c"&gt;# Or check the React Native directory for compatibility info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://reactnative.directory/" rel="noopener noreferrer"&gt;React Native directory&lt;/a&gt; now shows New Architecture compatibility for listed packages. Libraries that use the old NativeModules system need to be updated to TurboModules before they work correctly with the New Architecture enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling New Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// android/gradle.properties&lt;/span&gt;
&lt;span class="n"&gt;newArchEnabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In ios directory&lt;/span&gt;
&lt;span class="nv"&gt;RCT_NEW_ARCH_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 bundle &lt;span class="nb"&gt;exec &lt;/span&gt;pod &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Bridge-Compatible Pattern for Mixed Migration
&lt;/h3&gt;

&lt;p&gt;If you have custom native modules that are not yet migrated to TurboModules, the Bridge compatibility layer allows old and new modules to coexist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Accessing a not-yet-migrated module via the compatibility bridge&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NativeModules&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Old style — still works via compatibility bridge&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;LegacyDocumentModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// New style — direct JSI binding&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NativeDocumentProcessor&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./NativeDocumentProcessor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bridge compatibility layer is a migration tool, not a permanent solution. Native modules should be migrated to TurboModules progressively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problems the New Architecture Does Not Fix
&lt;/h2&gt;

&lt;p&gt;It is worth being specific about what the New Architecture does not change, because the marketing around it can create inflated expectations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript thread performance.&lt;/strong&gt; JSI removes the bridge overhead, but the JavaScript thread is still single-threaded. Expensive JavaScript computations still block the UI. The New Architecture does not fundamentally change this — it reduces the cost of JS-to-native communication, not the cost of JavaScript execution itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party library ecosystem gaps.&lt;/strong&gt; Many popular libraries have been slow to add New Architecture support. As of mid-2026, most major libraries support it, but you will still encounter edge cases. Always test your specific dependency tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complex gesture handling.&lt;/strong&gt; Gesture Responder System limitations are not primarily a bridge problem. The Gesture Handler library (now a recommended standard) addresses these, but it requires its own integration separate from New Architecture migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network performance.&lt;/strong&gt; Network calls go through the native networking stack regardless of architecture. The New Architecture does not make network calls faster.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4cobumeksg9vk6u2b36.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4cobumeksg9vk6u2b36.jpg" alt="React Native new architecture migration checklist showing five steps with progress indicators from enabling new architecture through to concurrent features.&amp;lt;br&amp;gt;
" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do Right Now
&lt;/h2&gt;

&lt;p&gt;If you are starting a new React Native project: New Architecture is enabled by default in React Native 0.74+. Leave it on. Start with TurboModules for any custom native code you write.&lt;/p&gt;

&lt;p&gt;If you have an existing app on React Native 0.71+: audit your third-party dependencies for compatibility, enable New Architecture in a feature branch, and test comprehensively against your specific hardware targets. Start with your most performance-critical screens.&lt;/p&gt;

&lt;p&gt;If you are on React Native below 0.71: upgrade first. The New Architecture on old React Native versions is a different, worse experience than on current versions.&lt;/p&gt;

&lt;p&gt;For a broader comparison of React Native and Flutter including how AI-assisted development changes the decision, read &lt;a href="https://www.lycore.com/blog/flutter-vs-react-native-ai-development/" rel="noopener noreferrer"&gt;Lycore's Flutter vs React Native comparison for 2026&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Lycore builds production React Native and Flutter applications for businesses building cross-platform mobile products. We architect, develop, and deliver mobile applications that perform reliably on real hardware. &lt;a href="https://www.lycore.com/contact-us/" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>javascript</category>
      <category>mobile</category>
      <category>typescript</category>
    </item>
    <item>
      <title>What a Real Digital Transformation Actually Looks Like for a Mid-Sized Business</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Sat, 25 Apr 2026 12:29:49 +0000</pubDate>
      <link>https://dev.to/lycore/what-a-real-digital-transformation-actually-looks-like-for-a-mid-sized-business-59a8</link>
      <guid>https://dev.to/lycore/what-a-real-digital-transformation-actually-looks-like-for-a-mid-sized-business-59a8</guid>
      <description>&lt;p&gt;Digital transformation is one of the most overused phrases in business. Consultants use it to sell strategy engagements. Software vendors use it to sell platforms. Conference speakers use it to describe any change involving technology. After enough repetition, it loses meaning entirely.&lt;br&gt;
This is unfortunate, because the underlying idea — using technology to fundamentally change how a business operates, not just automate what it already does — is genuinely valuable. The problem is not the concept. It is the way it gets packaged and sold.&lt;br&gt;
This article is about what a real digital transformation looks like for a mid-sized business: what actually happens, what typically goes wrong, what success looks like, and how to tell the difference between meaningful change and expensive redecorating.&lt;/p&gt;

&lt;p&gt;The Difference Between Digitisation and Transformation&lt;br&gt;
Before anything else, it is worth being clear about what transformation is not.&lt;br&gt;
Replacing paper forms with PDF forms is not transformation. Moving from a filing cabinet to a shared drive is not transformation. Building a website for a business that previously had no web presence is not transformation in the meaningful sense.&lt;br&gt;
These are digitisation — making existing processes electronic. They are often worth doing. They are not transformation.&lt;br&gt;
Transformation happens when technology enables a fundamentally different way of doing business — different processes, different capabilities, different competitive positioning — not just a faster or cheaper version of the existing approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46aaqh05qwf3juh3ceua.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46aaqh05qwf3juh3ceua.jpg" alt="Side-by-side comparison of business digitisation versus digital transformation showing the difference between automating existing processes and fundamentally redesigning how a business operates." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A manufacturer that replaces paper-based production tracking with a digital system is digitising. A manufacturer that uses real-time production data to dynamically adjust scheduling, predict maintenance needs, and optimise material ordering is transforming — because the technology has enabled something the business could not do before, not just faster execution of what it was already doing.&lt;br&gt;
The distinction matters because the investment, the timeline, and the organisational change required are fundamentally different. Digitisation projects are relatively predictable. Transformation is harder, takes longer, and fails more often — but produces results that cannot be achieved any other way.&lt;/p&gt;

&lt;p&gt;What Transformation Actually Requires&lt;br&gt;
The technology is usually the easiest part. This surprises most businesses when they hear it, but it is consistently true.&lt;br&gt;
Building a custom platform, integrating with existing systems, and migrating data is a tractable engineering problem. It has a known solution space, can be planned and estimated with reasonable accuracy, and follows predictable patterns. The hard parts of transformation are consistently the same across businesses and industries.&lt;br&gt;
Process redesign, not process automation. The instinct when digitalising a business process is to replicate the existing process in software. This instinct is almost always wrong. Existing processes were designed around the constraints of their medium — paper, phone calls, manual data entry — and they accumulate workarounds over years of operation. Digitalising a broken or inefficient process produces a faster broken or inefficient process.&lt;br&gt;
Real transformation starts with understanding what the process is trying to achieve, not how it currently works. From that understanding, you redesign — sometimes radically — and then build the technology that supports the redesigned process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fveh42jgjot98yigegr55.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fveh42jgjot98yigegr55.jpg" alt="Four-stage digital transformation journey roadmap showing audit, process redesign, technology build, and outcome measurement stages connected by a glowing path." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data readiness. Transformation initiatives that depend on data — which is most of them — fail far more often because of data quality problems than because of technology problems. A business that has been operating on spreadsheets for ten years has ten years of inconsistently formatted, partially duplicated, variably accurate data. Migrating this into a new system without a serious data cleaning exercise produces a new system full of bad data.&lt;br&gt;
Data readiness work is unglamorous, slow, and frequently underestimated in transformation projects. It is also non-negotiable. The businesses that do it properly before building technology produce better outcomes. The businesses that skip it spend months dealing with data quality issues after launch.&lt;br&gt;
Change management. The new system being technically complete and the organisation actually using it are different things. People who have been doing their jobs a particular way for years — sometimes decades — do not automatically adopt new approaches because the technology now supports them. Resistance, workarounds, and reversion to old habits are the default, not the exception.&lt;br&gt;
The businesses that succeed with transformation invest in change management as a first-class activity alongside technology development: clear communication about why the change is happening, involvement of the people affected in the design process, training that is role-specific and practical rather than generic, and visible leadership support that signals the new approach is not optional.&lt;/p&gt;

&lt;p&gt;What Success Looks Like, Measured&lt;br&gt;
Transformation that cannot be measured is indistinguishable from expensive change. Every transformation initiative should have a set of specific, measurable outcomes defined before the work starts — not "improved efficiency" but "reduced order processing time from 4 hours to 30 minutes" and "reduced error rate in invoicing from 8% to under 1%."&lt;br&gt;
These measurements serve two purposes. They tell you whether the transformation worked. And they create accountability for the initiative that prevents it from drifting into a technology project that runs forever without delivering business value.&lt;br&gt;
The most common transformation success metrics we see are: reduction in manual processing time (measurable in staff hours per week), reduction in error rates (measurable by type and frequency), improvement in customer-facing metrics (response time, satisfaction scores, churn), reduction in cost of a specific process (measurable per unit), and improvement in decision quality (measured by the quality of the information available to decision-makers).&lt;br&gt;
Choose three to five metrics before you start. Establish baselines. Measure at 30, 60, and 90 days after go-live. Adjust the approach based on what you find.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0750o4tv00ft6aej4vs7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0750o4tv00ft6aej4vs7.jpg" alt="Before and after KPI dashboard showing four business transformation metrics including order processing time, error rate, customer satisfaction, and cost per transaction all improving significantly." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Specific Failure Modes Worth Knowing&lt;br&gt;
Transformation initiatives fail in predictable ways. Knowing them in advance does not prevent them entirely, but it does make them easier to catch and correct.&lt;br&gt;
Scope expansion without timeline or budget adjustment. Transformation projects attract scope additions — stakeholders see the opportunity and want their needs included. Every addition that is not matched with timeline and budget adjustment increases risk. The discipline of maintaining a clear boundary around the MVP and managing additions through a structured change process is as important as any technical decision.&lt;br&gt;
Technology selection before process design. The vendor has a compelling platform. The demo looks good. The contract gets signed. Then the business discovers that the platform's assumptions about how the process should work do not match how the business actually needs to work. The sequence should always be: understand the process, design the new approach, then find the technology that fits.&lt;br&gt;
Going live without a parallel run period. Cutting over from an old system to a new one without any period of parallel operation is a high-risk approach. A parallel period — running both systems simultaneously for a defined period — is slower and more expensive but surfaces issues that only become apparent with real data and real users before the consequences are serious.&lt;br&gt;
Underestimating the training requirement. A two-hour training session for a system that people will use eight hours a day is not adequate preparation. Role-specific, practical training that covers not just how the system works but how to handle the edge cases specific to each role is the minimum. Ongoing support in the first weeks after go-live is essential.&lt;/p&gt;

&lt;p&gt;Where to Start&lt;br&gt;
For a mid-sized business beginning to think seriously about digital transformation, the most useful starting point is an honest audit of where your current operations are most constrained by technology limitations.&lt;br&gt;
Not where technology is absent — where it is actively constraining what the business can do. The process that everyone knows is broken but nobody has the capacity to fix. The data that exists but cannot be used because it is in the wrong system. The customer experience that is suffering because the internal tools cannot keep up with demand.&lt;br&gt;
That constraint is the right starting point. Not the most ambitious vision of what the business could be, not the most impressive technology available — the specific operational constraint that, if removed, would have the most measurable impact on the business.&lt;br&gt;
For businesses in the marketplace, e-commerce, or platform economy space, read &lt;a href="https://www.lycore.com/blog/maximizing-your-business-potential-with-a-custom-built-marketplace-platform/" rel="noopener noreferrer"&gt;Lycore's guide to maximising business potential with custom-built platforms&lt;/a&gt;  — the principles of building technology that fits your specific model, rather than conforming to what a generic platform allows, apply across every transformation initiative.&lt;/p&gt;

&lt;p&gt;Lycore is a custom software and AI development company with 20 years of engineering experience. We work with mid-sized businesses on digital transformation initiatives — from strategy through to delivery of custom platforms, AI integrations, mobile apps, and web applications. Get in touch.&lt;/p&gt;

</description>
      <category>product</category>
      <category>startup</category>
      <category>digital</category>
      <category>operations</category>
    </item>
    <item>
      <title>The Quiet Crisis in Residential Care: How Technology Is Helping Providers Get Ahead of Compliance Risk</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Sat, 25 Apr 2026 12:08:33 +0000</pubDate>
      <link>https://dev.to/lycore/the-quiet-crisis-in-residential-care-how-technology-is-helping-providers-get-ahead-of-compliance-1aa6</link>
      <guid>https://dev.to/lycore/the-quiet-crisis-in-residential-care-how-technology-is-helping-providers-get-ahead-of-compliance-1aa6</guid>
      <description>&lt;p&gt;In residential care, a compliance failure is not a minor administrative inconvenience. It is a CQC inspection finding. It is a safeguarding investigation. In the worst cases, it is harm to a resident that might have been prevented if the right information had been visible at the right time.&lt;br&gt;
The stakes are high and the administrative burden is significant. Residential care providers are required to maintain detailed records of incidents, medication administration, care plan reviews, and staff competencies — all while delivering care to residents with complex and changing needs, managing staff rotas, and operating within increasingly tight funding constraints.&lt;br&gt;
Most providers are doing this with a combination of legacy software, paper records, and spreadsheets that were never designed for the purpose. The result is a compliance environment that is reactive rather than proactive — problems are discovered at inspection rather than before it, incidents are logged after the fact rather than tracked as patterns, and the evidence of good care practice is fragmented across systems rather than readily accessible.&lt;br&gt;
Technology does not solve all of these problems. But the right technology, properly implemented, changes the compliance posture of a residential care provider from reactive to proactive — and that difference is significant both for residents and for the business.&lt;/p&gt;

&lt;p&gt;The Incident Management Problem Specifically&lt;br&gt;
Incident management is the clearest illustration of how the right technology changes compliance posture.&lt;br&gt;
In a reactive model, an incident occurs, a paper form is completed (often hours or days later, from memory), the form is filed, and unless there is an immediate regulatory notification requirement, that is the end of the process. The information exists but it is not systematically reviewed for patterns, not connected to care plan reviews, and not visible to the right people without manual effort.&lt;br&gt;
In a proactive model, incidents are logged digitally at the point of occurrence — on a mobile device, by the staff member involved, while the details are fresh. The log captures structured information: incident type, time, location, individuals involved, immediate actions taken, and a free-text description. The system automatically flags whether regulatory notification is required based on the incident type. Managers receive an alert. The incident is visible in the resident's care record immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop1cr07u34a4dq0bialw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop1cr07u34a4dq0bialw.jpg" alt="Split illustration contrasting reactive paper-based residential care compliance management with proactive digital incident management showing mobile logging and real-time alerts." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the real value comes in the pattern recognition that becomes possible over time. When incidents are logged consistently in a structured format, you can ask questions that paper records cannot answer. Are falls happening more frequently on a particular shift? Is a specific resident's incident rate increasing in a way that should trigger a care plan review? Is there a correlation between staffing levels and incident rates on particular days?&lt;br&gt;
These patterns exist in paper records too — but finding them requires someone to manually review hundreds of forms. In a digital system, they are surfaced automatically.&lt;/p&gt;

&lt;p&gt;Care Records and the CQC Evidence Question&lt;br&gt;
The Care Quality Commission's inspection framework requires providers to demonstrate not just that care is being delivered, but that it is person-centred, regularly reviewed, and responsive to changing needs. This is fundamentally an evidence question: what proof do you have that care planning is happening as it should?&lt;br&gt;
The challenge with paper-based care records is not that the care is not happening — in most providers, it is. The challenge is that the evidence is fragmented, inconsistent in quality, and difficult to retrieve during an inspection. A care plan review that happened six months ago is in a filing cabinet. The medication record from last Tuesday is in a different folder. The handover notes from last night's shift are in a book that might or might not be findable.&lt;br&gt;
A digital care management system solves the evidence problem by creating a single, searchable, timestamped record of care activity. Every care plan review is logged with who conducted it and what was changed. Every medication administration is recorded at the point of administration. Every handover note is captured digitally and visible to the incoming shift. When an inspector asks to see evidence that a resident's care plan was reviewed following a recent incident, the answer is a search rather than a manual hunt through filing cabinets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fublllq4h59jzyvuczilh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fublllq4h59jzyvuczilh.jpg" alt="Digital residential care management dashboard showing resident care records, incident log with status indicators, medication administration tracking, and staff compliance overview.&amp;lt;br&amp;gt;
" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Staff Competency and the Training Compliance Challenge&lt;br&gt;
Residential care providers are required to ensure that staff are trained and competent for the care they are delivering. This is straightforward in principle and genuinely challenging in practice, because training requirements are extensive, qualifications expire, mandatory updates recur annually, and rotas mean that not all staff can attend training on the same day.&lt;br&gt;
The manual version of managing this — spreadsheets tracking who has completed what by when, email reminders for upcoming renewals, paper certificates filed in staff records — is time-consuming, error-prone, and creates compliance gaps that are discovered at inspection rather than before it.&lt;br&gt;
A digital system that holds staff competency records, generates automatic alerts for approaching expiry dates, and produces evidence reports for inspection purposes removes most of the administrative burden and eliminates the most common compliance gaps. The system knows that a particular staff member's Safeguarding Level 2 renewal is due in 60 days and generates an alert — rather than the provider discovering it has lapsed when an inspector asks to see the certificate.&lt;/p&gt;

&lt;p&gt;The Integration Between Systems That Matters Most&lt;br&gt;
In residential care, the most important integration is between the incident management system and the care planning system — and it is the one that generic tools most often fail to deliver.&lt;br&gt;
When an incident occurs involving a specific resident, it should automatically prompt a review question: does this incident suggest that the current care plan is no longer appropriate? In paper-based systems, this connection depends entirely on a human remembering to make it. In a well-designed digital system, it happens automatically: an incident triggers a task for the named nurse or care coordinator to review the relevant sections of the care plan within a defined timeframe.&lt;br&gt;
This sounds like a small operational detail. Over time, it is one of the most significant drivers of care quality — because it means that the care plan remains a living document that reflects the resident's actual needs rather than a static record that was accurate six months ago.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwod8ezg11oao9a4bvdpl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwod8ezg11oao9a4bvdpl.jpg" alt="Flow diagram showing the digital incident management lifecycle in residential care from incident occurrence through logging, manager alerts, regulatory checks, care plan review, and quality improvement." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a detailed look at how technology can transform compliance and incident management in residential care settings, read &lt;a href="https://www.lycore.com/blog/experience-peace-of-mind-compliance-and-incident-management-for-residential-care/" rel="noopener noreferrer"&gt;Lycore's guide to peace of mind, compliance, and incident management for residential care.&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;What to Look for in a Residential Care Technology Partner&lt;br&gt;
Not all software built for residential care is built by people who understand residential care. The difference is visible in the details: whether the incident categories match CQC's framework, whether the medication recording workflow reflects actual administration practice, whether the care plan structure supports person-centred documentation rather than generic templates.&lt;br&gt;
The right technology partner for a residential care provider is one that has worked with providers directly, understands the regulatory environment, and builds software that reflects how care is actually delivered — not how an outside developer imagined it might be delivered.&lt;br&gt;
The outcome of getting this right is not just reduced administrative burden — though that is real and significant. It is a compliance posture that means inspections are opportunities to demonstrate good practice rather than anxious searches for missing documentation.&lt;/p&gt;

&lt;p&gt;Lycore is a custom software and AI development company with 20 years of engineering experience. We build care management platforms, compliance systems, AI integrations, and mobile applications for healthcare and social care providers. Get in touch.&lt;/p&gt;

</description>
      <category>socialcare</category>
      <category>compliance</category>
      <category>caremanagement</category>
      <category>regulatorytech</category>
    </item>
    <item>
      <title>The APIs Quietly Powering the Products You Use Every Day</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Sat, 25 Apr 2026 11:40:10 +0000</pubDate>
      <link>https://dev.to/lycore/the-apis-quietly-powering-the-products-you-use-every-day-1ldo</link>
      <guid>https://dev.to/lycore/the-apis-quietly-powering-the-products-you-use-every-day-1ldo</guid>
      <description>&lt;p&gt;When users interact with a product, they see the surface: the interface, the animations, the data that appears in front of them. What they do not see is the layer underneath — the network of APIs that make the product work. The payment processor that handles the transaction. The mapping service that plots the route. The AI model that generates the response. The identity provider that authenticates the user.&lt;br&gt;
Modern software products are not monolithic systems. They are compositions — carefully assembled networks of capabilities, most of which are delivered by APIs built and maintained by someone else. The quality of those API choices shapes the product's capabilities, its reliability, and its cost structure as fundamentally as any internal engineering decision.&lt;br&gt;
For developers building products today, understanding which APIs are genuinely worth integrating — and which ones represent technical debt in disguise — is an increasingly important skill.&lt;/p&gt;

&lt;p&gt;Why API Choices Matter More Than They Used To&lt;br&gt;
Ten years ago, most software products were built by teams that owned most of their stack. If you needed payments, you built a payment integration from the ground up. If you needed mapping, you licensed mapping data and built the rendering yourself. If you needed identity management, you built an authentication system.&lt;br&gt;
This approach was expensive, slow, and produced inconsistent quality. The infrastructure that underpins most modern products — payments, communications, mapping, AI, identity — is genuinely hard to build well. Stripe's payment infrastructure has had decades of investment and handles edge cases that most in-house payment teams would never encounter.&lt;br&gt;
The shift to API-first infrastructure means that a two-person startup can offer payment experiences that match large enterprises, because they are both using the same underlying payment API. It means a team of ten can build a product with mapping capabilities that would have required a GIS team of twenty in 2010.&lt;br&gt;
But this shift also means that API selection has become a genuine product and engineering discipline. The wrong API choice can mean vendor lock-in that costs you years to unwind, pricing that destroys unit economics at scale, reliability that caps your SLA, or capabilities that cannot grow with your product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjysoq41zry72jfycrqd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjysoq41zry72jfycrqd.jpg" alt="Network diagram showing a central software product connected to six surrounding API categories including payments, communications, mapping, AI, identity, and data services." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The APIs That Have Genuinely Changed What Is Possible&lt;br&gt;
Large language model APIs. The most significant shift in software capabilities in the last five years has come from LLM APIs — OpenAI, Anthropic, Google Gemini — making capabilities available via API that would previously have required a research team to build. Document understanding, semantic search, structured data extraction, conversational interfaces, code generation — all available at reasonable per-token costs through a standard API call.&lt;br&gt;
The products being built on LLM APIs right now are not theoretical demonstrations. They are automating document-heavy workflows in legal, financial, and healthcare businesses. They are powering customer service interfaces that handle tier-one queries without human intervention. They are enabling search experiences that understand intent rather than matching keywords.&lt;br&gt;
Real-time communication APIs. Twilio and its competitors made programmable SMS, voice, and WhatsApp available to any developer with a credit card. The business impact has been significant: appointment reminders that actually get seen (SMS open rates run at 90%+ versus email's 20%), transactional notifications that reach customers on the channel they prefer, two-way messaging flows that replace phone calls for routine communications.&lt;br&gt;
More recently, video API providers have made embedded video calling accessible at the API level — enabling telemedicine platforms, remote consultation tools, and virtual classroom products that deliver the experience natively within the product rather than redirecting users to third-party tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr5yn23r62aenaavlc6w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr5yn23r62aenaavlc6w.jpg" alt="Four API category cards showing LLM APIs, Communications APIs, Geolocation APIs, and Identity APIs with distinct icons and colour-coded glows on a dark navy background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Geolocation and mapping APIs. Google Maps and its alternatives — Mapbox, HERE, Apple Maps — have become infrastructure for any product where location is relevant. But the more interesting development is the layer above basic mapping: routing APIs that handle real-time traffic, geocoding APIs that resolve addresses to coordinates at scale, and distance matrix APIs that power logistics optimisation. For delivery platforms, field service management tools, and any product that coordinates physical assets across geography, these APIs are not a feature; they are the core operational layer.&lt;br&gt;
Payment infrastructure APIs. Stripe has been the reference implementation for developer-friendly payment APIs for over a decade, and its capabilities have expanded well beyond simple card processing. Stripe Connect powers marketplace payment flows — splitting payments between platforms and vendors, managing payouts, handling compliance across jurisdictions. Stripe Billing handles subscription management with all its edge cases: prorations, trials, upgrades, downgrades, metered billing. The availability of these capabilities via API has lowered the barrier to building marketplace and subscription businesses significantly.&lt;br&gt;
Identity and authentication APIs. Auth0, Clerk, and similar providers have turned authentication from an engineering problem into an API call. The value is not just the implementation time saved — it is the security capabilities that come bundled: MFA, social login, device management, anomaly detection, and compliance tooling that would take months to build to an equivalent standard.&lt;/p&gt;

&lt;p&gt;How to Evaluate an API Before You Build on It&lt;br&gt;
The excitement of a capable API can lead teams to integrate first and evaluate later. The consequences — lock-in, pricing surprises, reliability issues — show up months or years after the decision.&lt;br&gt;
The evaluation questions worth asking before integrating any significant API are: What is the pricing model at 10x and 100x your current volume, and is it sustainable? What is the documented SLA, and what compensation exists if it is breached? How active is the development and what is the deprecation policy for older API versions? What data does the API provider retain, and under what terms? And critically: if this API went away or became unaffordable, how would you replace it, and how long would that take?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnshnhyymt3gnkyjhm82.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnshnhyymt3gnkyjhm82.jpg" alt="API evaluation checklist showing five criteria including pricing at scale, SLA and reliability, version stability, data ownership, and replaceability on a dark background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The APIs that have become genuinely foundational — Stripe, Twilio, the major LLM providers — have earned that status through years of reliability, clear documentation, and pricing that remained reasonable as customers scaled. That track record is worth factoring into your evaluation alongside the technical capabilities.&lt;br&gt;
For a detailed look at the specific APIs shaping software development right now and how to evaluate which ones belong in your stack, read &lt;a href="https://www.lycore.com/blog/6-game-changing-apis-shaping-the-future-of-software-development/" rel="noopener noreferrer"&gt;Lycore's guide to the six game-changing APIs shaping the future of software development.&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Lycore is a custom software and AI development company with 20 years of engineering experience. We build AI integrations, API-connected applications, mobile apps, and web platforms for businesses that want practical results. Get in touch.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Why We Stopped Writing Boilerplate and What Our Code Reviews Look Like Now</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Thu, 16 Apr 2026 07:27:50 +0000</pubDate>
      <link>https://dev.to/lycore/why-we-stopped-writing-boilerplate-and-what-our-code-reviews-look-like-now-i45</link>
      <guid>https://dev.to/lycore/why-we-stopped-writing-boilerplate-and-what-our-code-reviews-look-like-now-i45</guid>
      <description>&lt;p&gt;A year and a half ago we started integrating AI tools into our development workflow across Python, Django, React, Flutter, and .NET projects. This is an honest account of what changed — specifically around boilerplate and code review, which are the two areas where the impact has been most concrete.&lt;br&gt;
Not a productivity manifesto. Just what actually happened.&lt;/p&gt;

&lt;p&gt;The boilerplate problem&lt;br&gt;
Before AI tools, setting up a new Django app involved a lot of typing that every senior engineer on our team had done hundreds of times. Serializers, viewsets, URL routing, permission classes, filter backends, initial migrations — none of it is hard, but it takes 30–45 minutes of focused work that adds zero intellectual value.&lt;br&gt;
We now generate that scaffolding in under two minutes. Here is roughly what a typical prompt looks like:&lt;br&gt;
Create a Django REST Framework setup for a &lt;code&gt;Project&lt;/code&gt; model with the &lt;br&gt;
following fields: name (CharField), owner (ForeignKey to User), &lt;br&gt;
status (choices: draft/active/archived), created_at, updated_at.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ModelSerializer with read-only id, created_at, updated_at&lt;/li&gt;
&lt;li&gt;ViewSet with list, retrieve, create, update, partial_update&lt;/li&gt;
&lt;li&gt;IsAuthenticated permission&lt;/li&gt;
&lt;li&gt;Filter by status and owner&lt;/li&gt;
&lt;li&gt;URL routing
The output is accurate, follows our patterns, and is ready for a senior engineer to review structurally before anything is built on top of it.
That last part — "ready for a senior engineer to review structurally" — is the rule we follow. AI writes the skeleton. A human checks the architecture before it becomes load-bearing. This stops structural mistakes from propagating through the codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What changed in code review&lt;br&gt;
This is the more interesting shift.&lt;br&gt;
Before AI tools, our code reviews caught a mix of things:&lt;/p&gt;

&lt;p&gt;Obvious mechanical issues: missing null checks, inefficient queries, typos in variable names&lt;br&gt;
Structural concerns: wrong abstraction level, coupling that shouldn't exist&lt;br&gt;
Business logic errors: misunderstood requirements, missing edge cases&lt;br&gt;
Security issues: missing validation, exposed data in serializers&lt;/p&gt;

&lt;p&gt;AI handles the first category well. We now run Claude over every PR before it goes to human review, with a prompt along these lines:&lt;br&gt;
Review this Django code for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;N+1 query problems&lt;/li&gt;
&lt;li&gt;Missing null/empty checks&lt;/li&gt;
&lt;li&gt;Serializer fields that expose sensitive data unintentionally&lt;/li&gt;
&lt;li&gt;Missing error handling in API views&lt;/li&gt;
&lt;li&gt;Any obvious security concerns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Code:&lt;br&gt;
[paste diff]&lt;br&gt;
It catches N+1 queries reliably. It spots missing select_related and prefetch_related calls. It flags serializer fields that probably shouldn't be writable. It notices when an exception is swallowed silently.&lt;br&gt;
This means our human reviewers spend almost no time on mechanical issues. They spend their time on the things AI is not good at: whether the abstraction is right, whether the business logic matches the actual requirement, whether this code will be maintainable in 18 months.&lt;/p&gt;

&lt;p&gt;The rule about tests&lt;br&gt;
One thing we learned the hard way: never let AI write both the implementation and the tests for the same code.&lt;br&gt;
If you do this, the tests pass — but they test what the code does, not what it should do. You end up with 100% coverage on the wrong behaviour.&lt;br&gt;
Our rule: tests are written against the specification, not against the implementation. We write test cases from requirements first (even just as comments describing what each test should verify), then use AI to fill in the test code. The human wrote the spec for the test. The AI wrote the boilerplate of the test function.&lt;br&gt;
This produces test suites that actually catch regressions rather than just confirming that the code runs.&lt;/p&gt;

&lt;p&gt;What the numbers look like&lt;br&gt;
We are careful about overstating productivity claims — the research on this is genuinely mixed and highly context-dependent. But across our team over the past year:&lt;/p&gt;

&lt;p&gt;New module setup time is down significantly. What took 45 minutes now takes under 10, including review.&lt;br&gt;
Code review cycles are shorter. Pre-review AI checks eliminate a full round of comments on mechanical issues in most PRs.&lt;br&gt;
Documentation coverage is higher. We generate initial API docs and changelog entries with AI after each sprint. This used to get skipped under time pressure.&lt;/p&gt;

&lt;p&gt;The gains are real. They are also concentrated in specific task types. Complex architectural decisions, novel integrations, debugging subtle race conditions — AI adds overhead on these, not speed. Knowing which is which is the actual skill.&lt;/p&gt;

&lt;p&gt;What we still do entirely by hand&lt;br&gt;
Security-critical code. Authentication systems, payment processing, access control logic, data encryption — we write these carefully, review them carefully, and do not rely on AI-generated implementations. Research shows a measurable increase in security vulnerabilities in AI-assisted code. We take that seriously.&lt;br&gt;
Novel architecture. When a project requires something that isn't a well-worn pattern — a custom multi-tenant data model, a real-time system with unusual constraints — AI suggestions tend toward generic solutions. These decisions need human judgment and experience.&lt;br&gt;
Anything that requires understanding the client's actual business. AI does not know why a field is named the way it is, what the edge case in the legacy data means, or why a particular design decision was made three years ago. That context lives in engineers' heads and in Slack history.&lt;/p&gt;

&lt;p&gt;The honest summary&lt;br&gt;
AI tools made our team faster on the tasks where they work well. They did not change what good engineering looks like — they just removed some of the friction in getting there.&lt;br&gt;
If you are adopting AI tools on your team: spend the time figuring out exactly where in your workflow they add value and where they add overhead. The teams seeing real gains are the ones who made that distinction deliberately, not the ones who turned on Copilot and hoped for the best.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.lycore.com" rel="noopener noreferrer"&gt;Lycore&lt;/a&gt;  is a custom software development company. We build with Django, React, Flutter, and .NET — and we have been integrating AI tools into our workflow since they became production-ready. Questions or thoughts? Drop them in the comments&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>codereview</category>
      <category>python</category>
    </item>
    <item>
      <title>Building AI Agents That Actually Work in Production: Lessons from Real Projects</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Thu, 16 Apr 2026 07:21:13 +0000</pubDate>
      <link>https://dev.to/lycore/building-ai-agents-that-actually-work-in-production-lessons-from-real-projects-2kl4</link>
      <guid>https://dev.to/lycore/building-ai-agents-that-actually-work-in-production-lessons-from-real-projects-2kl4</guid>
      <description>&lt;p&gt;Everyone is building AI agents right now. Most of them work fine in a demo and fall apart in production. This post is about the gap between the two — what causes it, and what we have learned from shipping agents that run reliably in real business environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "agent" actually means here
&lt;/h2&gt;

&lt;p&gt;For this post, an agent is a system that takes a goal, plans a sequence of steps to achieve it, executes those steps using tools or APIs, and adapts based on what it finds along the way — without a human approving each action.&lt;/p&gt;

&lt;p&gt;That is meaningfully different from a chatbot that answers questions, or a RAG pipeline that retrieves and summarises documents. An agent takes action in systems. That is what makes it powerful, and what makes it hard to get right in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four failure modes we see repeatedly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Unbounded loops
&lt;/h3&gt;

&lt;p&gt;An agent is given a task. It takes a step, evaluates the result, decides the task is not complete, takes another step — and loops. Without explicit loop limits, a poorly designed agent will happily consume API credits and time indefinitely.&lt;/p&gt;

&lt;p&gt;Every agent we build has a hard step limit. Not a soft suggestion — a hard ceiling that raises an exception and routes to human review if hit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;MAX_STEPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps_taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&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;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goal&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;context&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;goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history&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="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps_taken&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_decide_next_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;complete&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;success&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;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_execute_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps_taken&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="c1"&gt;# Exceeded step limit — escalate to human
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AgentStepLimitExceeded&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;Agent exceeded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; steps. Last context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Silent failures that look like success
&lt;/h3&gt;

&lt;p&gt;An agent calls an external API. The API returns a 200 with an error message in the body. The agent reads the response, interprets it as success, and reports the task complete.&lt;/p&gt;

&lt;p&gt;This is more common than it sounds. Many internal APIs have inconsistent error handling. Agents need to be taught to verify outcomes, not just record that an action was taken.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_action_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_type&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="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Verify the actual outcome of an action, not just that it returned.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;verifiers&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;send_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_record&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&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="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="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verifiers&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="n"&gt;action_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Unknown action type — log and flag for review
&lt;/span&gt;        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&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;No verifier for action type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;verifier&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;h3&gt;
  
  
  3. Prompt drift under load
&lt;/h3&gt;

&lt;p&gt;This one is subtle. Your agent works correctly on your test cases. In production, it sees inputs it has never encountered — slightly unusual phrasing, edge case data, requests that are adjacent to but not exactly what it was designed for. Over time, the average quality of outputs drifts.&lt;/p&gt;

&lt;p&gt;Mitigation requires ongoing monitoring of output quality, not just uptime. We log every agent decision alongside a confidence indicator and review anything below threshold. We also run a fixed set of regression test cases against the live agent weekly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tool call hallucinations
&lt;/h3&gt;

&lt;p&gt;The agent decides to call a tool that does not exist, or calls a real tool with parameters that do not match the schema. This fails with an error that confuses the agent, which then tries to recover and often makes things worse.&lt;/p&gt;

&lt;p&gt;The fix is strict tool schema enforcement with clear error messages that the agent can actually use to correct itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&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;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&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;tool_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;AVAILABLE_TOOLS&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&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;Tool &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; does not exist.&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;available_tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AVAILABLE_TOOLS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggestion&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;Check the tool name and try again.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AVAILABLE_TOOLS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;validation_errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validation_errors&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;error&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;Invalid parameters.&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;details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;validation_errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param_schema&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="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning structured, actionable error messages — rather than raising exceptions — gives the LLM something to work with when it makes a mistake.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture pattern we keep coming back to
&lt;/h2&gt;

&lt;p&gt;After building agents across fintech, healthcare, e-commerce, and SaaS projects, we have settled on a pattern that handles the above failure modes consistently:&lt;br&gt;
The key design decisions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate planning from execution.&lt;/strong&gt; The LLM proposes a plan. A separate step validates that plan against constraints (allowed tools, scope limits, business rules) before any action is taken. This stops the agent from taking actions it is not authorised to take.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintain explicit state.&lt;/strong&gt; The agent's understanding of what has happened and what still needs to happen is stored explicitly, not inferred from conversation history. This makes the state inspectable, debuggable, and resumable after failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human review queue is first-class.&lt;/strong&gt; It is not a fallback — it is a designed-in path. Any time the agent is uncertain, hits a limit, or encounters a situation outside its defined scope, it routes to the queue rather than guessing. This is what makes agents safe to run in production on real business data.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a minimal Django integration looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&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;STATUS_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pending&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;Pending&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;running&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;Running&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;complete&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;Complete&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;failed&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;Failed&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;needs_review&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;Needs Human Review&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="n"&gt;goal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;STATUS_CHOICES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;steps_taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&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="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSONField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&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="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&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;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&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;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&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;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&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="c1"&gt;# tasks.py (Celery task)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentTask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentStepLimitExceeded&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AgentTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&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="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&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;status&lt;/span&gt;&lt;span class="sh"&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="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LLM&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="n"&gt;executor&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;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;complete&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;task&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="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;result&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;AgentStepLimitExceeded&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="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;needs_review&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps_taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps_taken&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a persistent, inspectable record of every agent run — what it was trying to do, how far it got, what state it left things in, and whether it needs a human to look at it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability is not optional
&lt;/h2&gt;

&lt;p&gt;In production, you need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many agent tasks complete successfully vs. hit the step limit vs. fail?&lt;/li&gt;
&lt;li&gt;What is the distribution of steps taken per task?&lt;/li&gt;
&lt;li&gt;Which tool calls fail most often, and with what errors?&lt;/li&gt;
&lt;li&gt;What does a typical agent trajectory look like for your most common task types?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this data, you are flying blind. We instrument every tool call, every LLM decision, and every state transition, and pipe it to whatever monitoring stack the client is already using (Datadog, Sentry, CloudWatch — it does not matter as long as it's there).&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;Agents are genuinely useful. They can automate multi-step work that would otherwise require a human, and do it at a scale that changes what is possible for a business. But they require more careful engineering than a simple LLM API call.&lt;/p&gt;

&lt;p&gt;If you are building your first agent: start with a narrow, well-defined task. Add hard limits everywhere. Build the human review path first, not last. Instrument everything from day one. Expand scope only once the narrow case is working reliably in production.&lt;/p&gt;

&lt;p&gt;The teams that get agents into production successfully are not the ones who built the most ambitious agent — they are the ones who built the most observable one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://www.lycore.com/ai-development-services/" rel="noopener noreferrer"&gt;Lycore builds production AI systems&lt;/a&gt; for businesses — agents, RAG pipelines, LLM integrations, and custom AI applications on Django, React, Flutter, and .NET. &lt;a href="https://www.lycore.com/contact-us/" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt; if you want to talk through your use case.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>agents</category>
      <category>django</category>
    </item>
    <item>
      <title>LangChain vs LlamaIndex in 2026: What We Actually Use and Why</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Thu, 16 Apr 2026 07:02:42 +0000</pubDate>
      <link>https://dev.to/lycore/langchain-vs-llamaindex-in-2026-what-we-actually-use-and-why-52eb</link>
      <guid>https://dev.to/lycore/langchain-vs-llamaindex-in-2026-what-we-actually-use-and-why-52eb</guid>
      <description>&lt;p&gt;Every time we start a new AI project, someone on the team asks whether to use LangChain or LlamaIndex. We have shipped production systems with both. Here is an honest comparison based on that experience — not a feature matrix copied from the docs.&lt;/p&gt;

&lt;p&gt;Quick context&lt;br&gt;
Both libraries have matured significantly since their early versions. LangChain went through a messy period of API churn around v0.1/v0.2 that burned a lot of teams. LlamaIndex (formerly GPT Index) went deep on the retrieval and indexing use case. Both are now stable enough for production use, but they still have different strengths.&lt;/p&gt;

&lt;p&gt;What LangChain is actually good at&lt;br&gt;
Chains and agents with complex logic. LangChain's expression language (LCEL) is well-suited for building multi-step pipelines where you need explicit control over how data flows between components. If you are building an agent that needs to decide between multiple tools, maintain conversation state, and handle branching logic — LangChain's abstractions are a reasonable fit.&lt;br&gt;
Broad ecosystem of integrations. LangChain has integrations with almost everything: every major LLM, every vector store, most common APIs. If you need to wire together a lot of different services quickly, the breadth of LangChain's integration library saves time.&lt;br&gt;
Streaming support. LangChain's streaming support is solid and straightforward to implement, which matters for user-facing applications where you want tokens to appear as they are generated.&lt;br&gt;
A typical LangChain chain for a simple RAG 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_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PGVector&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIEmbeddings&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PGVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://user:pass@localhost/db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Answer based only on the context below.
If the answer isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t in the context, say so.

Context: {context}
Question: {question}
Answer:&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&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;context&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;question&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="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_chain_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&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;k&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="n"&gt;chain_type_kwargs&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;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;return_source_documents&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&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;What is the refund policy?&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;result&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;What LlamaIndex is actually good at&lt;br&gt;
Document ingestion and indexing. LlamaIndex's abstractions around loading, chunking, and indexing documents are more thought-through than LangChain's. If your primary use case involves ingesting varied document types — PDFs, Word docs, Notion pages, web pages, database rows — and querying across them, LlamaIndex handles this more cleanly.&lt;br&gt;
Query engines and advanced retrieval. LlamaIndex has better out-of-the-box support for more sophisticated retrieval patterns: hybrid search, recursive retrieval, query decomposition, and sub-question generation. If retrieval quality is your main concern, LlamaIndex gives you more levers to pull.&lt;br&gt;
Structured outputs from documents. Extracting structured data from unstructured documents is a use case where LlamaIndex's PropertyGraphIndex and structured extraction tools outperform what you'd put together yourself 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;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.retrievers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorIndexRetriever&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.query_engine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrieverQueryEngine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.postprocessor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimilarityPostprocessor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.llms.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.embeddings.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbedding&lt;/span&gt;

&lt;span class="c1"&gt;# Load and index documents
&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&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;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_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="n"&gt;embed_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIEmbedding&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&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;# Build query engine with similarity threshold
&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;VectorIndexRetriever&lt;/span&gt;&lt;span class="p"&gt;(&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;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;similarity_top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;postprocessor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimilarityPostprocessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity_cutoff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetrieverQueryEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;node_postprocessors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;postprocessor&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAI&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_engine&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;What is the refund policy?&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="n"&gt;response&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_nodes&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where each falls short&lt;br&gt;
LangChain:&lt;br&gt;
The abstraction layers can work against you. When something breaks in a LangChain chain, the stack traces are deep and the error messages are not always helpful. Debugging a complex agent that is misbehaving takes longer than it should because you are reasoning through multiple layers of abstraction rather than your own code.&lt;br&gt;
API stability has improved but LangChain still moves fast. Pinning your versions carefully and reading changelogs before upgrading is not optional.&lt;br&gt;
The breadth of integrations is also a maintenance burden — many community integrations are not well-maintained and break quietly when upstream services change.&lt;br&gt;
LlamaIndex:&lt;br&gt;
Steeper initial learning curve. LlamaIndex's concepts (nodes, indices, query engines, postprocessors) take longer to internalise than LangChain's chain/agent model.&lt;br&gt;
Less suited for complex agentic workflows. If you need an agent that takes sequential actions across multiple external tools — not just retrieval — LangChain or building directly on the OpenAI/Anthropic function calling API is usually cleaner.&lt;br&gt;
The documentation has improved a lot but some of the advanced features are still better understood by reading source code than docs.&lt;/p&gt;

&lt;p&gt;What we actually reach for&lt;br&gt;
For RAG on existing Django/Python apps: We often skip both and build directly with the OpenAI API and pgvector, as described in our RAG guide. For straightforward retrieval use cases the framework overhead is not worth it.&lt;br&gt;
For document-heavy use cases with varied inputs: LlamaIndex. The document loaders and chunking pipeline alone justify it.&lt;br&gt;
For multi-step agents or complex chains: LangChain, specifically LCEL. The composability is genuinely useful for orchestrating multi-step logic.&lt;br&gt;
For anything client-facing where we need fine control: Neither. We build directly on the provider APIs (OpenAI, Anthropic) with our own thin wrappers. This gives us complete control over prompts, error handling, costs, and debugging.&lt;/p&gt;

&lt;p&gt;The honest take&lt;br&gt;
Both are tools, not commitments. The mistake most teams make is picking one and trying to use it for everything. LangChain and LlamaIndex have different strengths; the right one depends on your specific use case.&lt;br&gt;
If you are building your first AI feature: start without a framework. Call the API directly. Add a framework when the complexity justifies it, not before.&lt;/p&gt;

&lt;p&gt;Lycore builds production AI systems for businesses — RAG pipelines, agents, LLM integrations, and custom AI applications. We work with both LangChain and LlamaIndex depending on the project requirements. Get in touch if you want to talk through your use case.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>langchain</category>
      <category>llm</category>
    </item>
    <item>
      <title>Building a RAG Pipeline on Your Existing Django App: A Practical Guide</title>
      <dc:creator>Lycore Development</dc:creator>
      <pubDate>Wed, 15 Apr 2026 15:03:26 +0000</pubDate>
      <link>https://dev.to/lycore/building-a-rag-pipeline-on-your-existing-django-app-a-practical-guide-4a5b</link>
      <guid>https://dev.to/lycore/building-a-rag-pipeline-on-your-existing-django-app-a-practical-guide-4a5b</guid>
      <description>&lt;p&gt;You have a Django app. You want to add AI-powered search or a chatbot that actually knows about your data — not just a generic LLM that hallucinates answers. The solution is RAG: Retrieval-Augmented Generation.&lt;/p&gt;

&lt;p&gt;This guide walks through exactly how we add RAG to existing Django projects. No rebuilding from scratch. No switching stacks. Just a clean integration on top of what you already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What RAG actually does
&lt;/h2&gt;

&lt;p&gt;A plain LLM call like GPT-4 or Claude knows only what it was trained on. Ask it about your product catalogue, your customers, or your internal docs — and it will either make something up or tell you it doesn't know.&lt;/p&gt;

&lt;p&gt;RAG fixes this by retrieving relevant chunks of your actual data and injecting them into the prompt as context. The model then answers based on that context, not just training data.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User asks a question&lt;/li&gt;
&lt;li&gt;Your app converts the question to a vector embedding&lt;/li&gt;
&lt;li&gt;You search your vector store for the most similar chunks of your data&lt;/li&gt;
&lt;li&gt;Those chunks get injected into the LLM prompt as context&lt;/li&gt;
&lt;li&gt;The LLM answers based on your data&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Django app with a PostgreSQL database (we'll use &lt;code&gt;pgvector&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An OpenAI API key (for embeddings and completions)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pgvector&lt;/code&gt; Postgres extension installed&lt;/li&gt;
&lt;li&gt;Python packages: &lt;code&gt;openai&lt;/code&gt;, &lt;code&gt;pgvector&lt;/code&gt;, &lt;code&gt;django&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use &lt;code&gt;pgvector&lt;/code&gt; here because it runs inside your existing Postgres instance — no separate vector database to manage or pay for. For larger scale, Pinecone or Chroma are worth evaluating, but for most Django projects pgvector is the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install pgvector
&lt;/h2&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;pgvector openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the extension in Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add a vector field to your model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pgvector.django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorField&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VectorField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&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;blank&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;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py makemigrations
python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Generate and store embeddings
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# utils/embeddings.py
&lt;/span&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;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&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;get_embedding&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&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;response&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hook this into a Django signal:&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="c1"&gt;# signals.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db.models.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;post_save&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.utils.embeddings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_embedding&lt;/span&gt;

&lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_save&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Document&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;generate_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;created&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Semantic search with pgvector
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# utils/search.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pgvector.django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CosineDistance&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.utils.embeddings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_embedding&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;semantic_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;CosineDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="n"&gt;top_k&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="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Build the RAG response
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# utils/rag.py
&lt;/span&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;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;semantic_search&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&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;rag_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;relevant_docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;semantic_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&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;Title: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;relevant_docs&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a helpful assistant. Answer the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s question
using ONLY the context provided below. If the answer is not in the context,
say so clearly — do not make up information.

Context:
{context}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;system&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system_prompt&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&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;answer&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&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="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;relevant_docs&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;h2&gt;
  
  
  Step 6: Wire it up as a Django view
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# views.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JsonResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.decorators.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;require_POST&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.decorators.csrf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csrf_exempt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.utils.rag&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;rag_response&lt;/span&gt;

&lt;span class="nd"&gt;@require_POST&lt;/span&gt;
&lt;span class="nd"&gt;@csrf_exempt&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&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;data&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JsonResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;query is required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&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="nf"&gt;rag_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JsonResponse&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;Test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/api/ask/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "What is your refund policy?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Things to watch in production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Chunking strategy matters.&lt;/strong&gt; Split long documents into 300–500 token chunks with 50–100 token overlap before embedding. Add a &lt;code&gt;DocumentChunk&lt;/code&gt; model alongside &lt;code&gt;Document&lt;/code&gt; for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache embeddings aggressively.&lt;/strong&gt; Never regenerate an embedding for content that hasn't changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor retrieval quality.&lt;/strong&gt; Log cosine distance scores. If the top result has a distance above ~0.4, handle this gracefully rather than feeding irrelevant context to the LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limit your LLM calls.&lt;/strong&gt; Use the &lt;code&gt;tenacity&lt;/code&gt; library for retry logic with exponential backoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this gets you
&lt;/h2&gt;

&lt;p&gt;A working RAG pipeline on your existing Django app, using your existing Postgres database, with no new infrastructure. The whole integration is around 100 lines of code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;At &lt;a href="https://www.lycore.com" rel="noopener noreferrer"&gt;Lycore&lt;/a&gt; we build AI integrations on top of existing Django, React, and .NET applications. If you're looking to add RAG, agents, or other AI capabilities to your stack, &lt;a href="https://www.lycore.com/contact-us/" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>rag</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
