<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Marvelous MLOps Substack]]></title><description><![CDATA[Serving you expertise MLOps content]]></description><link>https://www.marvelousmlops.io</link><image><url>https://substackcdn.com/image/fetch/$s_!S63_!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png</url><title>Marvelous MLOps Substack</title><link>https://www.marvelousmlops.io</link></image><generator>Substack</generator><lastBuildDate>Tue, 14 Apr 2026 09:49:13 GMT</lastBuildDate><atom:link href="https://www.marvelousmlops.io/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Marvelous MLOps]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[marvelousmlops@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[marvelousmlops@substack.com]]></itunes:email><itunes:name><![CDATA[MarvelousMLOps]]></itunes:name></itunes:owner><itunes:author><![CDATA[MarvelousMLOps]]></itunes:author><googleplay:owner><![CDATA[marvelousmlops@substack.com]]></googleplay:owner><googleplay:email><![CDATA[marvelousmlops@substack.com]]></googleplay:email><googleplay:author><![CDATA[MarvelousMLOps]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[What 300+ Engineers from Netflix, Amazon, and Instacart Asked About AI Engineering]]></title><description><![CDATA[The Top 10 questions (and answers) from 4 cohorts of Building AI Applications]]></description><link>https://www.marvelousmlops.io/p/what-300-engineers-from-netflix-amazon</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/what-300-engineers-from-netflix-amazon</guid><dc:creator><![CDATA[Hugo Bowne-Anderson]]></dc:creator><pubDate>Fri, 06 Mar 2026 07:03:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!u6qA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Over the past year and four cohorts, we&#8217;ve taught <strong>over 300 Builders</strong> from <strong>Netflix</strong>, <strong>Amazon</strong>, <strong>Instacart</strong>, and more how to <strong>build AI-Powered Applications</strong>. These are the <strong>10 most common questions</strong> they&#8217;ve asked. This is from a list of <strong>100 question and answers</strong> that we share with <strong>all students in the course</strong>.</em></p><p>&#128073; <strong>These are also the kinds of things we cover in our Building AI Applications course. Our final cohort starts March 9. <a href="https://maven.com/hugo-stefan/building-ai-apps-ds-and-swe-from-first-principles?promoCode=coding-agent">Here is a 25% discount code for readers</a>.</strong> &#128072;</p><p><a href="https://maven.com/p/d83502/building-ai-applications-the-reader">Also check out Chapter 1</a> of our <strong>300 page course reader</strong> covering <strong>evaluation</strong>, <strong>testing</strong>, <strong>RAG</strong>, <strong>agents</strong>, <strong>multimodal systems</strong>, <strong>fine-tuning</strong>, <strong>production deployment</strong>, and more.</p><p>Each question below has a <strong>short, concise answer</strong> and we then provide <strong>more detail</strong> in an &#8220;<strong>In practice&#8221;</strong> section.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u6qA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u6qA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 424w, https://substackcdn.com/image/fetch/$s_!u6qA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 848w, https://substackcdn.com/image/fetch/$s_!u6qA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 1272w, https://substackcdn.com/image/fetch/$s_!u6qA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u6qA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png" width="1456" height="821" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:821,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:361504,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://hugobowne.substack.com/i/189722023?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!u6qA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 424w, https://substackcdn.com/image/fetch/$s_!u6qA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 848w, https://substackcdn.com/image/fetch/$s_!u6qA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 1272w, https://substackcdn.com/image/fetch/$s_!u6qA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc785240b-f830-47f9-9722-10fd9fc0cc18_2262x1276.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Our Building AI Applications Course Curriculum Snake (because Python!)</figcaption></figure></div><p><em>Builders mentioned in this post were students, builders in residence, or guest speakers in the course.</em></p><p><em>This post was co-written by Hugo Bowne-Anderson, Stefan Krawczyk, Pastor Soto, and Mike Powers. Mike and Pastor were instrumental in building the pipeline that took 1,000s of Discord messages and workshop transcripts to extract the questions (and parts of the answers!)</em></p><p>The top 10 questions were:</p><ul><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms">Q1:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms"> How do I get </a><strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms">reliable</a></strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms"> and </a><strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms">consistent outputs</a></strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms"> from </a><strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms">LLMs</a></strong><a href="https://hugobowne.substack.com/i/189722023/q1-how-do-i-get-reliable-and-consistent-outputs-from-llms">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">Q2:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems"> What&#8217;s the difference between an </a><strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">LLM</a></strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">, </a><strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">augmented LLMs</a></strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">, </a><strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">LLM workflows</a></strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">, </a><strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">agents</a></strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">, and </a><strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">multi-agent systems</a></strong><a href="https://hugobowne.substack.com/i/189722023/q2-whats-the-difference-between-an-llm-augmented-llms-llm-workflows-agents-and-multi-agent-systems">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q3-what-is-an-agent-harness">Q3:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q3-what-is-an-agent-harness"> What is an </a><strong><a href="https://hugobowne.substack.com/i/189722023/q3-what-is-an-agent-harness">Agent Harness</a></strong><a href="https://hugobowne.substack.com/i/189722023/q3-what-is-an-agent-harness">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q4-when-do-i-use-retrievalrag-vs-context-engineering">Q4:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q4-when-do-i-use-retrievalrag-vs-context-engineering"> When do I use </a><strong><a href="https://hugobowne.substack.com/i/189722023/q4-when-do-i-use-retrievalrag-vs-context-engineering">Retrieval/RAG</a></strong><a href="https://hugobowne.substack.com/i/189722023/q4-when-do-i-use-retrievalrag-vs-context-engineering"> vs </a><strong><a href="https://hugobowne.substack.com/i/189722023/q4-when-do-i-use-retrievalrag-vs-context-engineering">Context Engineering</a></strong><a href="https://hugobowne.substack.com/i/189722023/q4-when-do-i-use-retrievalrag-vs-context-engineering">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt">Q5:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt"> How do I </a><strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt">choose</a></strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt"> which </a><strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt">frameworks</a></strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt"> and </a><strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt">tools</a></strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt"> to </a><strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt">adopt</a></strong><a href="https://hugobowne.substack.com/i/189722023/q5-how-do-i-choose-which-frameworks-and-tools-to-adopt">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">Q6:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively"> What are </a><strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">guardrails</a></strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively"> in </a><strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">LLM applications</a></strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively"> and </a><strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">how</a></strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively"> do I </a><strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">implement</a></strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively"> them </a><strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">effectively</a></strong><a href="https://hugobowne.substack.com/i/189722023/q6-what-are-guardrails-in-llm-applications-and-how-do-i-implement-them-effectively">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q7-how-do-i-write-deterministic-tests-for-non-deterministic-llm-outputs">Q7:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q7-how-do-i-write-deterministic-tests-for-non-deterministic-llm-outputs"> How do I </a><strong><a href="https://hugobowne.substack.com/i/189722023/q7-how-do-i-write-deterministic-tests-for-non-deterministic-llm-outputs">write deterministic tests</a></strong><a href="https://hugobowne.substack.com/i/189722023/q7-how-do-i-write-deterministic-tests-for-non-deterministic-llm-outputs"> for </a><strong><a href="https://hugobowne.substack.com/i/189722023/q7-how-do-i-write-deterministic-tests-for-non-deterministic-llm-outputs">non-deterministic LLM outputs</a></strong><a href="https://hugobowne.substack.com/i/189722023/q7-how-do-i-write-deterministic-tests-for-non-deterministic-llm-outputs">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">Q8:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation"> What is an </a><strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">MVE</a></strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation"> (</a><strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">Minimum Viable Eval</a></strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">) and how do I </a><strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">get started</a></strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation"> with </a><strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">AIevaluation</a></strong><a href="https://hugobowne.substack.com/i/189722023/q8-what-is-an-mve-minimum-viable-eval-and-how-do-i-get-started-with-ai-evaluation">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs">Q9:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs"> What is an </a><strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs">LLM Judge</a></strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs"> and how do I use them to </a><strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs">evaluate</a></strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs"> other </a><strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs">LLM outputs</a></strong><a href="https://hugobowne.substack.com/i/189722023/q9-what-is-an-llm-judge-and-how-do-i-use-them-to-evaluate-other-llm-outputs">?</a></p></li><li><p><strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models">Q10:</a></strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models"> </a><strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models">When</a></strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models"> and </a><strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models">how</a></strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models"> should I </a><strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models">fine-tune models</a></strong><a href="https://hugobowne.substack.com/i/189722023/q10-when-and-how-should-i-fine-tune-models">?</a></p></li></ul><h2>Q1: How do I get reliable and consistent outputs from LLMs?</h2><p><em><strong>You don&#8217;t eliminate non-determinism, you build processes around it</strong></em>. LLMs are fundamentally stochastic: same input, different output. The practitioners who ship reliable LLM applications have accepted this and invest in three things:</p><p>1) <strong>prompt &amp; context engineering</strong> with <strong>tight feedback loops</strong>,</p><p>2) <strong>structured outputs</strong> and <strong>post-processing validation</strong>, and</p><p>3) <strong>systematic evaluation</strong> and <strong>testing</strong>.</p><h3>In practice</h3><p><strong>Start with your prompts and context.</strong> Treat them like code: version control them, change one variable at a time, and iterate based on observed failures. Nathan Danielsen (AI Engineer, Carvana) did over 600 iterations on an initial MVP production prompt (and hundreds more after launch). Use system prompts to set behavior, delimiters to separate instructions from data, and structured output modes (JSON, Pydantic schemas) to enforce format consistency. Also look at distributions of prompt results by sending the same prompt to the system 20 times (for example).</p><p><strong>Build evaluation and testing from day one.</strong> <a href="https://youtube.com/live/Vz4--82M2_0?feature=share">The highest-value activity is putting traces in a spreadsheet, labeling them pass/fail, and categorizing where things go wrong</a>. Start with 20 representative queries. This manual inspection reveals failure modes that no automated system will catch on its own. From there, scale with code-based checks (regex, schema validation, length checks) and LLM-as-judge evaluators, always preferring deterministic checks where they work.</p><p><strong>Design for modularity.</strong> Break large tasks into smaller, more constrained sub-tasks. One example in the course is a veterinary transcription app that improved dramatically by giving the LLM specific, smaller extraction tasks rather than one big request. This reduces variance and makes each piece independently testable. Build so you can swap models, change prompts, and add guardrails without rewriting everything.</p><p><strong>Don&#8217;t use an LLM when you don&#8217;t need one.</strong> Regex for date extraction, fuzzy matching libraries for string similarity, etc&#8230;: these are deterministic, cheaper, and often more reliable than an LLM call for specific tasks.</p><p><strong>The bottom line:</strong> <em>you will never have certainty with these systems. What you can have is a process that catches most failures before they reach your users.</em></p><p><em><strong>Important note:</strong> </em>As William Horton (MLE, Maven Clinic) pointed out, non-determinism doesn&#8217;t matter if the result is the same. For example, a medical chatbot asked &#8220;What should I do for a mild fever?&#8221; might respond &#8220;Rest, stay hydrated, and take acetaminophen: see a doctor if it exceeds 103&#176;F&#8221; or it might respond &#8220;An OTC pain reliever like acetaminophen can help, along with fluids and rest: consult your physician if the fever passes 103&#176;F.&#8221; The wording is different but the medical guidance is identical. The non-determinism is cosmetic.</p><h2>Q2: What&#8217;s the difference between an LLM, augmented LLMs, LLM workflows, agents, and multi-agent systems?</h2><ul><li><p>An <strong>LLM</strong> is <strong>stateless</strong>: text in, text out with no memory or context (unless specified in the input);</p></li><li><p>An <strong>augmented LLM </strong>adds <strong>memory</strong> to the system, <strong>retrieves information</strong> to ground the system (via RAG, for example), and/or <strong>adds tool calls</strong> (such as pinging an API, sending an email, or writing code to your local file system). <a href="https://www.anthropic.com/engineering/building-effective-agents">As Anthropic points out</a>, virtually all LLMs in production have &#8220;access to these augmented capabilities&#8221;;</p></li><li><p>An <strong>LLM workflow</strong> is a system where <strong>LLMs</strong> and <strong>tools</strong> are <strong>orchestrated</strong> through <strong>predefined code paths</strong>: you as the developer decide what happens when;</p></li><li><p>An<strong> Agent</strong> is a system where the <strong>LLM dynamically directs its own processes</strong> and <strong>tool usage</strong>. This takes the shape of an LLM with tools in a loop (see Q3 on Agent Harnesses);</p></li><li><p>A <strong>Multi-agent system</strong> is a <strong>design pattern</strong> in which a <strong>primary orchestrating agent</strong>delegates specialized, &#8220;heavy-duty&#8221; tasks to <strong>autonomous sub-agents</strong> to maintain system performance and focus.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LHaT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LHaT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 424w, https://substackcdn.com/image/fetch/$s_!LHaT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 848w, https://substackcdn.com/image/fetch/$s_!LHaT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 1272w, https://substackcdn.com/image/fetch/$s_!LHaT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LHaT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png" width="1456" height="606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:606,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!LHaT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 424w, https://substackcdn.com/image/fetch/$s_!LHaT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 848w, https://substackcdn.com/image/fetch/$s_!LHaT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 1272w, https://substackcdn.com/image/fetch/$s_!LHaT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6da9e9f-88e8-42f1-9cba-9d929c9922ab_1600x666.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Evaluator-Optimizer LLM Workflow Pattern (<a href="https://www.anthropic.com/engineering/building-effective-agents">from here</a>)</figcaption></figure></div><h3>In practice</h3><p><strong>The practical marker between LLM workflows and Agents:</strong> in an LLM workflow, the control flow is a DAG (directed acyclic graph) with predictable steps. In an agent, there&#8217;s a loop where the LLM decides what tool to call next, what to do with the result, and when to stop.</p><p><strong>Where most value lives today.</strong> <a href="https://www.decodingai.com/p/stop-building-ai-agents">The augmented LLM and LLM workflows: an LLM with retrieval, memory, and a handful of tool calls covers the vast majority of production use cases</a>. A meeting transcription bot that converts speech to text, summarizes, and emails minutes? People call it an agent because they get an email from it, but it&#8217;s really a workflow. If a simple tool call works, don&#8217;t build a fully-fledged agent.</p><p><strong>When agents make sense.</strong> Agents shine for open-ended problems where you can&#8217;t predict the required steps in advance: research is the canonical example. Deep Research, Claude Code, and similar systems are genuinely agentic because the path is truly dynamic. But this comes with real costs: brittleness, complexity creep, and misaligned autonomy.</p><p><strong>The two cultures of AI agents.</strong> In the course, we teach these two approaches: (1) build reliable software with workflows and few tool calls, strong evaluation, and predictable behavior; (2) build high-agency AI collaborators with strong human supervision. The danger zone is high agency with low supervision. Both cultures require evaluation and observability.</p><h2>Q3: What is an Agent Harness?</h2><p><strong><a href="https://hugobowne.substack.com/p/how-to-build-a-general-purpose-ai">An agent harness</a></strong> is the scaffolding around the LLM that manages tool execution, message history, and context. It is the infrastructure that wraps around an LLM to turn it into an agent.</p><h3>In practice</h3><p><strong>Recall that an agent is a system where the LLM dynamically directs its own processes and tool usage</strong>: Agents are just LLMs with tools in an agentic loop. There is often a conversational loop with the user also.</p><p><em>The <strong>agent harness</strong> handles the</em></p><ul><li><p><strong>Loop:</strong> prompting the model, parsing its output, executing tools, feeding results back</p></li><li><p><strong>Tool execution</strong>: actually running the code/commands the model asks for</p></li><li><p><strong>Context management:</strong> what goes in the prompt, token limits, history</p></li><li><p><strong>Safety/guardrails:</strong> confirmation prompts, sandboxing, disallowed actions</p></li><li><p><strong>State:</strong> keeping track of the conversation, files touched, etc.</p></li></ul><p>And more.</p><p><em><strong>Think of it like this:</strong></em> the LLM is the brain, the harness is everything else that lets it actually do things.</p><p><strong><a href="https://hugobowne.substack.com/p/ai-agent-harness-3-principles-for">Embrace Harness Re-architecture:</a></strong> Teams must be willing to constantly reassess and rebuild. The popular agent Manus has been re-architected five times since March 2024, and LangChain&#8217;s Open Deep Research was rebuilt multiple times in a year to keep pace with model improvements. Even Anthropic rips out Claude Code&#8217;s agent harness as models improve!</p><p><strong><a href="https://hugobowne.substack.com/p/how-to-build-a-general-purpose-ai">The hello world of agent harnesses:</a></strong> this covers the loop, tool execution, and basic context management. What it doesn&#8217;t have: safety guardrails, token limits, persistence, or even a system prompt!</p><h4>Two other interesting and powerful harnesses</h4><p><strong><a href="https://github.com/badlogic/pi-mono">The Pi Coding Agent</a>:</strong> adds context loading AGENTS.md from multiple directories, persistent sessions you can resume and branch, and an extensibility system (skills, extensions, prompts);</p><p><strong><a href="https://openclaw.ai/">OpenClaw (which builds on Pi):</a></strong> a persistent daemon (always-on, not invoked), chat as the interface (Telegram, WhatsApp, etc.), file-based continuity (SOUL.md, MEMORY.md, daily logs), proactive behavior (heartbeats, cron), pre-integrated tools (browser, sub-agents, device control), and the ability to message you without being prompted.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZnUB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZnUB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZnUB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZnUB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZnUB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZnUB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg" width="1408" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1408,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ZnUB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZnUB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZnUB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZnUB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9683786c-edb0-4cc8-8458-25c49f5de6db_1408x768.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">How to hand-roll the agentic loop (from <a href="https://hugobowne.substack.com/p/how-to-build-a-general-purpose-ai">here</a>)</figcaption></figure></div><p>Thanks for reading Vanishing Gradients! Subscribe for free to receive new posts and support my work.</p><p>Subscribe</p><h2>Q4: When do I use Retrieval/RAG vs Context Engineering?</h2><p>These aren&#8217;t competing approaches: they&#8217;re tools at different levels of a system.</p><ul><li><p><strong><a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">Context engineering</a></strong> refers to &#8220;the set of strategies for curating and maintaining the optimal set of tokens (information/context) during LLM inference, including all the other information that may land there outside of the prompts.&#8221;</p></li><li><p><strong>RAG</strong> is one technique doing this via <strong>retrieval</strong>, that is, adding information from external sources to the context you provide the LLM.</p></li></ul><h3>In practice</h3><p><strong>Context engineering is the real skill.</strong> <a href="https://x.com/karpathy/status/1937902205765607626?lang=en">Andrej Karpathy&#8217;s definition</a>: &#8220;the delicate art and science of filling the context window with just the right information for the next step.&#8221; We tend to agree as the term &#8220;prompt engineering&#8221; trivializes what is actually an engineering discipline. Every system, whether it uses RAG, tool calls, or hand-curated context, is doing context engineering. The question is always: what information does the model need to produce a good response for this specific step?</p><blockquote><blockquote><p>&#8220;Context engineering does a few things. Number one, it <em>is</em> the job today. If you&#8217;re looking to build a production system, that is the job: <em>engineer your context window. </em>Prompt engineering&#8217;s like script kitty versus context engineering&#8217;s more like software engineering.&#8221;</p></blockquote><blockquote><p>&#8211; <a href="https://hugobowne.substack.com/p/the-rise-of-agentic-search">Jeff Huber (Chroma, Guest Talk in the course)</a></p></blockquote></blockquote><p><strong>Why you can&#8217;t just dump everything into long context.</strong> Context rot is real. <a href="https://research.trychroma.com/context-rot">See Jeff Huber&#8217;s research at Chroma</a>: as input length increases, model performance degrades significantly: down to ~50% accuracy at 10,000 tokens for several simple tasks, even for frontier models that claim million-token contexts. Needle-in-a-haystack benchmarks don&#8217;t reflect real-world complexity. The practical threshold: William Horton (ML Engineer, Maven Clinic) suggests that if your entire corpus fits into ~100k tokens, just dump it in. Beyond that, retrieval remains essential. But there&#8217;s a cost dimension too: sending more tokens than necessary costs money, and running evaluations on RAG systems has caused some of the biggest cost overages William&#8217;s team has seen.</p><p><strong>Start with Hybrid Search</strong>: Combining lexical search (like BM25) with semantic vector search is a robust default strategy. Their strengths and weaknesses are complementary, often leading to better recall and precision out of the box; <a href="https://www.youtube.com/watch?v=l9V1IN8mQIg&amp;t=461s">see guest talk in our course from Jeff Huber (Chroma) here for more</a>.</p><p><strong>Agentic Retrieval/RAG: the model decides when and how to search.</strong> Here, instead of the system stuffing retrieved context into the initial prompt, the model itself determines via tool calling when to search and what queries to use. This is what people mean by &#8220;agentic RAG.&#8221; The retrieval isn&#8217;t going away: it&#8217;s moving from the application layer into the model&#8217;s decision loop. <a href="https://hugobowne.substack.com/p/episode-68-a-builders-guide-to-agentic">As John Berryman (Early Engineer on Github Copilot) said</a>: &#8220;<strong>not only does the agent determine when to search, but it can determine how to search</strong>. If you do it right, the agent has a better understanding of your corpus and the user&#8217;s information needs, and it can do things like better filter through the searches, use more appropriate synonyms and search techniques, and do parallel searches that are more likely to get to the information the user wants.&#8221;</p><p><strong><a href="https://hugobowne.substack.com/p/ai-agent-harness-3-principles-for">Context engineering becomes critical in multi-agentic systems:</a></strong> leading agentic systems like Manus and Claude Code leverage three main techniques to manage context effectively (in addition to writing memory to file):</p><ol><li><p><strong>Reduce:</strong> Actively shrink the context passed to the model. This can be done by <strong>compacting older tool calls</strong> (keeping only a summary) or using trajectory summarization to compress the entire history once it reaches a certain size.</p></li><li><p><strong>Offload:</strong> Move information and complexity out of the prompt. This includes saving full tool results to an external file system for later reference. More profoundly, it means <strong>offloading the action space</strong>. Instead of giving an agent 100 different tools (which bloats the prompt), give it a few atomic tools like a bash terminal. This allows the agent to execute a vast range of commands without cluttering the context.</p></li><li><p><strong>Isolate:</strong> Use <strong>multi-agent architectures</strong> to delegate token-heavy sub-tasks. A main agent can offload a complex job to a specialized sub-agent, which performs the work in its own isolated context and returns only a concise result.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!muEV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!muEV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 424w, https://substackcdn.com/image/fetch/$s_!muEV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 848w, https://substackcdn.com/image/fetch/$s_!muEV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 1272w, https://substackcdn.com/image/fetch/$s_!muEV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!muEV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png" width="1456" height="518" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:518,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!muEV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 424w, https://substackcdn.com/image/fetch/$s_!muEV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 848w, https://substackcdn.com/image/fetch/$s_!muEV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 1272w, https://substackcdn.com/image/fetch/$s_!muEV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f32794c-aebc-4f9e-a8c1-973899ba7cc4_1456x518.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Context engineering techniques (<a href="https://hugobowne.substack.com/p/ai-agent-harness-3-principles-for">from here</a>)</figcaption></figure></div><h2>Q5: How do I choose which frameworks and tools to adopt?</h2><ul><li><p><strong>Start with vanilla Python and direct API calls</strong> so you understand what your system actually does;</p></li><li><p><strong>Only adopt a framework when it solves a specific pain point</strong> you&#8217;ve already experienced: premature framework adoption is the fastest path to proof-of-concept purgatory;</p></li><li><p><strong>Choose frameworks that abstract away</strong> the code you don&#8217;t want to write or think about;</p></li><li><p><strong>Do NOT choose frameworks that abstract away</strong> what you need to see in your system (<a href="https://hamel.dev/blog/posts/prompt/">e.g. the prompt</a>);</p></li><li><p><strong>Choose frameworks based on who builds them</strong> and their track record of building OSS dev tools and communities.</p></li></ul><h3>In practice</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iS13!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iS13!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 424w, https://substackcdn.com/image/fetch/$s_!iS13!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 848w, https://substackcdn.com/image/fetch/$s_!iS13!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 1272w, https://substackcdn.com/image/fetch/$s_!iS13!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iS13!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png" width="1413" height="965" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:965,&quot;width&quot;:1413,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!iS13!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 424w, https://substackcdn.com/image/fetch/$s_!iS13!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 848w, https://substackcdn.com/image/fetch/$s_!iS13!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 1272w, https://substackcdn.com/image/fetch/$s_!iS13!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb6d09f6-b24c-4f30-8276-f73d1c5392e1_1413x965.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Path to Proof of Concept Purgatory</figcaption></figure></div><p><strong>The proof-of-concept purgatory problem.</strong> Using frameworks prematurely will get you in POC purgatory. They&#8217;re great for demos: with a framework, you can build a RAG system demo in five lines of code. But it&#8217;s exactly this abstraction that traps teams: you can&#8217;t see what prompt was sent to the LLM, you can&#8217;t inspect the retrieval results, you can&#8217;t iterate on the pieces that matter. <em>A question from Hugo:</em> &#8220;How can we even think about iterating quickly on this MVP when we have no access to all the inner workings?&#8221;</p><p><strong>Start with vanilla Python and direct API calls.</strong> This is our core position in the course, and it comes from watching the same pattern play out repeatedly: teams adopt a framework, build something quickly, ship it, and then 6-12 months later either sunset the product or rip out the framework and rebuild from API calls: this time understanding what they actually need.</p><p><strong>When to add a framework.</strong> Only when it demonstrably improves outcomes. After you understand your system&#8217;s behavior through direct API calls, you&#8217;ll know which parts are tedious and error-prone. That&#8217;s where a framework earns its place. Make sure to adopt frameworks that abstract away the code you don&#8217;t want to write yourself. And when you do adopt tooling, prioritize observability: being able to see what your system is doing is more important than which framework orchestrates it.</p><p><strong>Frameworks help you think, not do your job.</strong> Stefan&#8217;s framing resets the relationship: frameworks are conceptual scaffolding. They organize your thinking about what components a system needs. But you still have to control the API calls, the prompts, the evaluation. And underneath every framework, there&#8217;s an API call being made somewhere: remembering that grounds you when the hype gets thick.</p><p><strong>The observability pattern.</strong> We see nearly all production use cases requiring some custom evaluation, logging, and tracing. The most common pattern is an observability tool (Pydantic + Logfire, Braintrust, Arize, etc.) combined with custom instrumentation. Observability tooling is being commoditized: so choose tools that integrate well and let you switch easily, rather than locking into a platform.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xz-C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xz-C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 424w, https://substackcdn.com/image/fetch/$s_!xz-C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 848w, https://substackcdn.com/image/fetch/$s_!xz-C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 1272w, https://substackcdn.com/image/fetch/$s_!xz-C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xz-C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png" width="1088" height="1140" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1140,&quot;width&quot;:1088,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:244550,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://hugobowne.substack.com/i/189722023?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!xz-C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 424w, https://substackcdn.com/image/fetch/$s_!xz-C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 848w, https://substackcdn.com/image/fetch/$s_!xz-C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 1272w, https://substackcdn.com/image/fetch/$s_!xz-C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F921e4cb1-16db-4cd1-8760-39e82d5f7c5a_1088x1140.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://x.com/samhogan/status/1914877291920023591">On Frameworks</a></figcaption></figure></div><h2>Q6: What are guardrails in LLM applications and how do I implement them effectively?</h2><p>Guardrails are programmatic checks you run before or after an LLM call. There are two places to put guardrails:</p><ol><li><p><strong>Checking the input </strong>before it reaches the LLM (prompt injection detection, PII filtering, scope validation) and</p></li><li><p><strong>Checking the output</strong> after (hallucination detection/groundedness, format validation).</p></li></ol><p>That&#8217;s it.</p><h3>In practice</h3><p><strong>What guardrails actually are.</strong> They range from simple if-else checks (is the output suspiciously short?) to regex (does the input contain a social security number?) to ML models (a fine-tuned BERT for prompt injection detection) to another LLM call (is this response faithful to the provided context?). Off-the-shelf guardrail libraries are just combinations of these same building blocks and we recommend against blindly adopting these; they are at best often too generic to provide strong value in specific use cases and can also mislead.</p><p><strong>Start with the system prompt, then add programmatic checks.</strong> The cheapest guardrail is a well-written system prompt: &#8220;don&#8217;t make medical claims,&#8221; &#8220;cite sources,&#8221; &#8220;stay on topic.&#8221; Many production systems leak their system prompts, and they start with pages of &#8220;do not do this.&#8221; For input guardrails, delimiters (XML tags, triple backticks) help prevent prompt injection by clearly separating instructions from user data. For output guardrails, use code-based checks before reaching for an LLM judge:they&#8217;re faster, cheaper, and deterministic. In the end, you also need to be running tests (See Q7 below).</p><p><strong>Make guardrails modular.</strong> Build them so you can rip them out and replace them. The guardrails you need will change as your product evolves, as models improve, and as you discover new failure modes.</p><p><strong>Guardrails come in layers, and the layers have different properties.</strong> <em>Static filters</em>(regex, blocklists, bloom filter matching) are fast and cheap but brittle. Guest Speaker Katharine Jarmul (Privacy expert) showed how GitHub Copilot&#8217;s code guardrails (which use bloom filter-style matching to block copyrighted code) were bypassed by simply changing variable names to French. <em>Algorithmic guardrails</em> (classifiers like Llama Guard, LLM-as-judge evaluators) are more nuanced but add latency and cost, and have their own failure modes that need their own evaluation. And then there&#8217;s alignment (RLHF, constitutional AI) which shapes model behavior at training time. Most robust, least transparent, and not something most practitioners control directly. <em>Effective systems layer all three:</em> static filters catch the obvious stuff fast, model-based classifiers handle nuance, alignment sets the behavioral baseline. Katharine&#8217;s architecture:</p><p>input processing &#8594; algorithmic guardrails &#8594; LLM &#8594; algorithmic guardrails &#8594; software guardrails &#8594; user.</p><p><strong>Real-world failure modes.</strong> ML Engineer William Horton&#8217;s team at Included Health built a customer support bot using internal documents, and it leaked direct phone numbers that were only supposed to be used by support agents. The system wasn&#8217;t malicious: it was doing exactly what it was trained to do, just with insufficient guardrails on what information should be exposed.</p><p><strong>&#8220;Guardrails&#8221; means different things to different people.</strong> Nicola Roberts (AI Governance Lead, Australia Post) made a point that resonated across cohorts: when executives say &#8220;do we have guardrails in place?&#8221;, they&#8217;re not asking about input/output checks. They&#8217;re asking about compliance, risk appetite, accountability, and fallback plans. It&#8217;s a marketing term for organizational coverage as much as it is a technical concept.</p><h2>Q7: How do I write deterministic tests for non-deterministic LLM outputs?</h2><p><em><strong>You DON&#8217;T write deterministic tests for non-deterministic outputs</strong></em>. You write tests that account for variance, you separate what can be checked deterministically with code (valid JSON, correct format, banned words absent) from what requires judgment (tone, relevance), and you track pass rates over time rather than expecting 100%.</p><h3>In practice</h3><p><strong>Don&#8217;t aim for 100% pass rates.</strong> This is the hardest mindset shift for software engineers. With traditional software, tests should always pass. With LLM applications, an 85% pass rate might be excellent. The goal is to track whether that number goes up or down when you make changes. If your entire test suite passes 100% of the time, your tests aren&#8217;t hard enough: add harder cases. Put another way: <em><strong>think &#8220;evaluation&#8221; not &#8220;test.&#8221;</strong></em></p><p><strong>Run the same call multiple times to characterize variance.</strong> Before deciding if an output is &#8220;wrong,&#8221; understand the distribution. Stefan recommends calling the same prompt multiple times and looking at how much the output varies. Some fields (like a person&#8217;s name) should always be correct. Others (like a skills summary) will have acceptable variance.</p><p><strong>Build curated datasets that define your product&#8217;s boundaries.</strong> Your test dataset isn&#8217;t just for testing: it defines what your product should and shouldn&#8217;t do. Start with ~20 examples for an MVE, grow to ~100 for more confidence. Use failures you encounter during development as new test cases.</p><p><strong>Use a test runner as infrastructure, but think &#8220;evaluation&#8221; not &#8220;test.&#8221; </strong>You want parameterized cases, CI integration, and fixtures. pytest is the common choice in Python; use whatever your team knows. The key shift: your LLM checks are evaluations with acceptable failure rates, not tests that must always pass.</p><p><strong>Two loops, one process. </strong>Your dev tests and production evals should be the same system. Don&#8217;t build a pytest suite for dev and a separate eval pipeline for prod. Same tests, same data format, same pass rate tracking. CI runs the suite before deploy; production traces feed new test cases back into development.</p><p><strong>Run all your test cases, even when some fail.</strong> Default test behavior is to stop at the first failure, but for LLM evals you need the full picture. Run everything, collect all outputs, then analyze patterns: &#8220;name extraction passes 95%, skills summary passes 60%.&#8221; That pattern recognition beats chasing individual failures. In pytest, the pytest-harvest plugin does this; other runners have equivalents.</p><h2>Q8: What is an MVE (Minimum Viable Eval) and how do I get started with AI evaluation?</h2><p><strong>An MVE (Minimum Viable Eval) is a set of input-output pairs for your AI system plus a script that runs your system&#8217;s outputs against that set</strong>. The script can include code checks (string matching, regex, structured output validation) and LLM-as-judge calls. Together, they form your <strong>eval harness</strong>: the thing that tells you whether a change made your system better or worse.</p><h3>In practice</h3><p>You don&#8217;t need to (and shouldn&#8217;t) jump to the most sophisticated evaluation frameworks immediately. <strong>There is a ladder of evaluation</strong>:</p><ul><li><p><em><strong>Vibe checks</strong></em></p></li><li><p><em><strong>Failure Analysis</strong></em></p></li><li><p><em><strong>Automated evals / Eval Harness (code checks + LLM Judges)</strong></em></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DDo6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DDo6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 424w, https://substackcdn.com/image/fetch/$s_!DDo6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 848w, https://substackcdn.com/image/fetch/$s_!DDo6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 1272w, https://substackcdn.com/image/fetch/$s_!DDo6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DDo6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png" width="1456" height="820" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!DDo6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 424w, https://substackcdn.com/image/fetch/$s_!DDo6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 848w, https://substackcdn.com/image/fetch/$s_!DDo6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 1272w, https://substackcdn.com/image/fetch/$s_!DDo6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe31b64eb-f08a-45dd-a1bd-1f9aa0af00b5_1600x901.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Vibe checks</h4><p><strong>Start by looking at your data.</strong> Before you automate anything, <a href="https://www.youtube.com/live/Vz4--82M2_0?feature=share">look at 20 input-output pairs from your system</a>. Why 20? It&#8217;s a number that seems doable and people almost always get interested when they do that: they see patterns, discover failure modes they hadn&#8217;t anticipated, and want to look at more! At this point, you can iterate quickly.</p><h4>Failure Analysis</h4><p><strong>Building your MVE.</strong> From there, build up to 50-100 samples for your MVE. The experts in the space (Shreya Shankar, Hamel Husain) want at least 100 data points. Natalia Rodnova, working in healthcare, needs thousands. Nathan, AI Engineer at a Fortune 500, reviews 1,200+ samples along various dimensions for each project. But start with 50-100! Iterate as you go: <em>what you want is <strong>representative coverage</strong> over what <strong>types of inputs your users provide</strong></em>.</p><p><strong>Spreadsheets &amp; pivot tables.</strong> Pass the input samples through your system. Label the responses by hand: accept or reject, with a reason and a failure mode classification. Put it in a spreadsheet. Do a pivot table. You now have failure modes ranked by frequency, and that tells you what to fix first. Hugo acknowledges that people hate it when he tells them to use spreadsheets and pivot tables, but it works.</p><p><strong>Pro tip: you don&#8217;t need users to begin.</strong> Generate synthetic queries by specifying who your user is, what their goal is, and what scenario they&#8217;re in.</p><h4>Automated evals</h4><p><strong>When to add automated evals &amp; LLM judges.</strong> Start with human annotation first. Only move to automated judges after you&#8217;ve done enough manual labeling to understand your quality criteria and failure modes. Build the judge by defining clear evaluation dimensions, providing few-shot examples or heuristic rules, and then aligning the judge against your human labels. The judges aren&#8217;t a replacement for understanding your data: they&#8217;re a way to scale that understanding (more on LLM judges below in Q8).</p><h4>What&#8217;s most important</h4><p><strong>The virtuous cycle.</strong> Deploy, observe behavior, make improvements, ship them, observe again. Evaluation drives this loop. One builder in the course reported going from 86.79% to 94.74% F1 score across three iterations using an eval harness. Nathan Danielsen spends 70-80% of his time on evaluations for Fortune 500 AI projects: &#8220;Changing the prompt is a very small part, compared to proving out with some good evaluations that the change to the prompt is measurable and will scale.&#8221;</p><blockquote><blockquote><p>&#8220;<em><strong>If your eyes are bleeding from spreadsheets, you&#8217;re probably doing LLM evaluation right</strong></em>.&#8221; &#8212; <a href="https://www.youtube.com/watch?v=JDMzdaZh9Ig">Philip Carter (Honeycomb, Guest Talk in Course)</a></p></blockquote></blockquote><h2>Q9: What is an LLM Judge and how do I use them to evaluate other LLM outputs?</h2><p><strong>An LLM judge is just a prompt that scores another LLM&#8217;s output:</strong> the key is aligning it with human labels through iteration (run the judge, hand-label the same outputs blind, compare, and refine the judge prompt until they agree), and building one judge per failure mode rather than one judge for everything.</p><h3>In practice</h3><p><strong>Do NOT start with judges or automated evals</strong>. Use the ladder of evaluation (see Q8 above).</p><ul><li><p><strong>Use code-based checks for reference-based metrics,</strong> i.e. when we have a ground-truth answer (such as &#8220;was the correct tool called by an AI travel assistant?&#8221;)</p></li><li><p><strong>Use LLM Judges for reference-free metrics,</strong> i.e. when a &#8220;correct&#8221; response is subjective or there can multiple correct responses, such as &#8220;did the travel assistant have a helpful tone?&#8221; or &#8220;did the medical information assistant offer a diagnosis?&#8221;</p></li></ul><p>The most important step in using an LLM Judge is aligning with your own judgements and/or those of an SME</p><p><strong>Start with code-based checks.</strong> Before reaching for a judge, ask: can I check this with code? JSON schema validation, regex pattern matching, length checks, keyword matching: these are deterministic, cheap (or free), and fast. These are great for <em>reference-based metrics</em> (when we have a ground-truth answer), such as &#8220;was the correct tool called by an AI travel assistant?&#8221;</p><p><strong>Use judges for subjective criteria: </strong>Use LLM judges for things that require judgment: tone, relevance, faithfulness to context, quality of reasoning. Also consider using them when there can multiple correct responses.</p><p><strong>Align judges with humans:</strong> The alignment loop is the most important step. Once you&#8217;ve built your judge prompt: (1) run it on new outputs, (2) hand-label the same outputs yourself, blind, (3) compare human vs. judge labels, (4) iterate on the judge prompt where they disagree. <a href="https://vanishinggradients.fireside.fm/51">Philip Carter (then Honeycomb, now Salesforce) saw alignment go from 68% to 82% to 94% after 3 iterations</a>. One builder in the course saw judge accuracy jump from 65% to 94% after several iterations on the course project.</p><h4>Pro tips</h4><p><strong>One judge per failure mode.</strong> Don&#8217;t build a single judge that evaluates everything. If your failure modes include &#8220;hallucinated facts,&#8221; &#8220;wrong tone,&#8221; and &#8220;missing key information,&#8221; build three separate judges, each with its own prompt, criteria, and examples. This mirrors how you&#8217;d assign human reviewers: you wouldn&#8217;t ask one person to evaluate everything at once.</p><p><strong>Use pass/fail, not Likert scales.</strong> Binary evaluation is more reliable and forces clearer criteria. LLMs aren&#8217;t great at distinguishing a 3 from a 4 on a 5-point scale. Pass or fail. If it passed, move on. If it failed, categorize why.</p><p><strong>Don&#8217;t worry about which model to use.</strong> You&#8217;ll get far more lift from the alignment process than from model selection. Use the cheapest model that works. Note there&#8217;s some evidence of self-preference bias (Gemini rates Gemini higher), but the prompt matters more than the model.</p><p><strong>Know when you need a judge vs. when you don&#8217;t.</strong> You need a judge when: you&#8217;ve done enough manual annotation to understand your failure modes, you need to evaluate at scale, or you want continuous production monitoring. You don&#8217;t need a judge to check if JSON is valid or if a name was extracted correctly.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!z9ey!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!z9ey!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 424w, https://substackcdn.com/image/fetch/$s_!z9ey!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 848w, https://substackcdn.com/image/fetch/$s_!z9ey!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 1272w, https://substackcdn.com/image/fetch/$s_!z9ey!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!z9ey!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png" width="1456" height="820" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!z9ey!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 424w, https://substackcdn.com/image/fetch/$s_!z9ey!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 848w, https://substackcdn.com/image/fetch/$s_!z9ey!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 1272w, https://substackcdn.com/image/fetch/$s_!z9ey!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d33acfe-3150-43cc-97f3-849d4ab2dc2d_1600x901.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The LLM Judge Alignment Protocol</figcaption></figure></div><h2>Q10: When and how should I fine-tune models?</h2><p><em><strong>Try everything else first</strong></em>: most production use cases are better served by prompting, retrieval, context engineering, and structured outputs. Fine-tuning still makes sense for <strong>small models</strong> on <strong>edge devices</strong> or <strong>specialized domains</strong> with <strong>poor training coverage</strong>, but it comes with real costs in infrastructure, maintenance, and reduced flexibility.</p><h3>In practice</h3><p><strong>What fine-tuning actually is.</strong> It&#8217;s continuing a pre-trained model&#8217;s training on a new, task-specific dataset. The canonical example: base (completion) models don&#8217;t answer questions; ask &#8220;what is the capital of France?&#8221; and they&#8217;ll generate a list of similar questions, because that&#8217;s what their training data looks like. Instruction tuning fine-tunes on question-answer pairs, giving us the chat models we use daily. That particular kind of fine-tuning has already been done for you by the big vendors.</p><p><strong>When it can still make sense.</strong> <a href="https://youtube.com/live/jx4Ts-yoy2w?feature=share">Small models on edge devices are the clearest use case</a>: if you need a model running locally on a phone with low latency, you can&#8217;t use a frontier API model, and fine-tuning a smaller model for your specific task fills that gap. <em>Specialized data domains where the training corpus has poor coverage are another case</em>. In a guest talk in our July cohort last year, Zach Mueller (then technical lead for Hugging Face Accelerate) gave two more examples: <em>knowledge distillation</em> and <em>low-resource languages</em>.</p><p><strong>Why not to do it.</strong> Infrastructure costs, headcount costs, and maintenance costs aren&#8217;t cheap. And when a new model comes out, do you retrain? Fine-tuning also reduces flexibility: the model may handle expected inputs well but perform worse on unexpected ones. The practical reality is that fine-tuning is costly in money, time, and ongoing maintenance.</p><p><strong>The protocol if you do it.</strong> Get a base model. Prepare your data (collection, cleaning, labeling, curation: this is the hardest part). Choose parameter-efficient fine-tuning (LoRA is the standard approach). Train. Monitor the loss function. Test the output manually. Then evaluate formally. Frameworks like Axolotl (for beginners) and Unsloth (for limited GPU resources) handle the plumbing.</p><div id="youtube2-jx4Ts-yoy2w" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;jx4Ts-yoy2w&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/jx4Ts-yoy2w?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>&#128073; <strong>These are also the kinds of things we cover in our Building AI Applications course. Our final cohort starts March 9. <a href="https://maven.com/hugo-stefan/building-ai-apps-ds-and-swe-from-first-principles?promoCode=coding-agent">Here is a 25% discount code for readers</a>.</strong> &#128072;</p><p><a href="https://maven.com/p/d83502/building-ai-applications-the-reader">Also check out Chapter 1</a> of our 300 page course reader covering evaluation, testing, RAG, agents, multimodal systems, fine-tuning, production deployment, and more.</p><p><em>Thank you to William Horton, Nathan Danielsen, Bradley Morris, Natalia Rodnova, and John Berryman for providing feedback on drafts of this post.</em></p><p><em>Also many thanks to the 300+ builders who asked these wonderful questions in our Building AI Applications course!</em></p>]]></content:encoded></item><item><title><![CDATA[UV All the Way: Your Go-To Python Environment Manager]]></title><description><![CDATA[In 2025 the best way to manage Python projects is using uv. This tutorial helps you set up a modern Python project from scratch using uv.]]></description><link>https://www.marvelousmlops.io/p/uv-all-the-way-your-go-to-python</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/uv-all-the-way-your-go-to-python</guid><dc:creator><![CDATA[Boldizsár]]></dc:creator><pubDate>Mon, 10 Nov 2025 07:28:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sn9C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In 2025 the best way to manage Python projects is using <a href="https://docs.astral.sh/uv/">uv</a>. This tutorial helps you set up a modern Python project from scratch using uv.</p><blockquote><p><em>Yep, correct spelling is </em>uv<em>, two lower-case letters.</em></p></blockquote><p>The two most important things that uv manages for you is a project-specific virtual environment (venv), and all the project dependencies in that venv.</p><p>Let&#8217;s start creating stuff!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sn9C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sn9C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 424w, https://substackcdn.com/image/fetch/$s_!sn9C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 848w, https://substackcdn.com/image/fetch/$s_!sn9C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!sn9C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sn9C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png" width="1456" height="1047" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1047,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sn9C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 424w, https://substackcdn.com/image/fetch/$s_!sn9C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 848w, https://substackcdn.com/image/fetch/$s_!sn9C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!sn9C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2093a572-800c-40ab-bc4d-b07a7d7e4685_1458x1048.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Install uv</h3><p>If you haven&#8217;t already, install uv on your machine. Open a shell or terminal to run these commands:</p><ul><li><p>On MacOS, make sure you have <a href="https://brew.sh/">Homebrew installed</a>, then run <code>brew install uv</code> to install uv.</p></li><li><p>On Linux, run <code>curl -LsSf https://astral.sh/uv/install.sh | sh</code> to install uv.</p></li><li><p>On Windows, run <code>powershell -ExecutionPolicy ByPass -c &#8220;irm https://astral.sh/uv/install.ps1 | iex&#8221;</code> to install uv.</p></li><li><p>(Otherwise consult the <a href="https://docs.astral.sh/uv/getting-started/installation/">uv installation docs</a>).</p></li></ul><p>Make sure you have uv installed and available as a command line application. You should be able to open a new shell and run the following command:</p><pre><code>$ uv --version
uv 0.9.5 (Homebrew 2025-10-21)</code></pre><p>By the time you read this, uv will be updated many times, so you should expect to see a newer version.</p><h3>Create a project with uv</h3><p>Let&#8217;s create a personal Python project for you using uv.</p><p>In this example we&#8217;ll pretend you&#8217;re caled &#8220;Alice&#8221; and use that as an example. Feel free to sub Alice with your own name or whatever name you want to use for this project.</p><p>Let&#8217;s open the shell again, and create a project folder. After that let&#8217;s change into that folder and run all further commands <em>inside</em> that folder.</p><pre><code>mkdir -p alice-python
cd alice-python</code></pre><p>(On Windows use Powershell, and you can omit the <code>-p</code> flag to <code>mkdir</code>.)</p><p>We&#8217;ll <code>uv init</code> to initialize the project. Run <code>uv init --help</code> to see the available options.</p><pre><code>$ uv init --name alice-python --python 3.13 --description &#8220;Alice&#8217;s personal Python project&#8221;
Initialized project `alice-python`</code></pre><p>(Again, you can use your own name and description instead of Alice.)</p><p>You can inspect the contents of the <code>alice-python</code> directory.</p><pre><code>$ ls -A
.git
.gitignore
.python-version
main.py
pyproject.toml
README.md</code></pre><p>Open the folder in your favorite editor, such as <a href="https://code.visualstudio.com/">Visual Studio Code</a>, <a href="https://www.jetbrains.com/pycharm/">PyCharm</a>, or <a href="https://zed.dev/">Zed</a>. Review the files that <code>uv init</code> has created for you:</p><ul><li><p>It created a <code>.git</code> folder and added a standard <code>.gitignore</code> file to help track your project in Git.</p></li><li><p>It created a <code>pyproject.toml</code> which is the standard (not uv-specific!) config file for Python projects.</p></li><li><p>It created a <code>main.py</code> which is just a dummy file to get you started. We&#8217;ll edit this later.</p></li><li><p>It created a <code>.python-version</code> file which tells uv which version of Python to use.</p></li><li><p>It created an empty <code>README.md</code> which you could also use to take notes during this tutorial.</p></li></ul><h3>venv setup</h3><p>Go back from the editor to the terminal.</p><p>Run the following to create and sync the virtual environment (venv):</p><pre><code>$ uv sync
Using CPython 3.13.3
Creating virtual environment at: .venv
Resolved 1 package in 17ms
Audited in 0.34ms</code></pre><blockquote><p><em>A virtual environment (venv) is a folder (inside the </em><code>.venv</code><em>) that contains all the dependencies of the project. The reason we use them is to isolate the dependencies of this project from the dependencies installed globally on your computer and to isolate it from the dependencies of other projects.</em></p></blockquote><p>List the files again or take a look at the folder structure in your editor.</p><pre><code>$ ls -A
.git
.gitignore
.python-version
.venv
alice
main.py
pyproject.toml
README.md
uv.lock</code></pre><p>See that:</p><ul><li><p>A <code>uv.lock</code> file has been generated which locks (in other word: pins) all dependencies to a current version.</p></li><li><p>A <code>.venv</code> folder has been created and it now contains a valid Python virtual environment. (It&#8217;s very similar to a venv you would have created with <code>python3 -m venv .venv</code>.)</p></li></ul><blockquote><p><em>The </em><code>uv sync</code><em> command does all of the following:</em></p><p>* If the requested Python version is not installed on your computer, it will be installed once.<br>* If the venv (in the <code>.venv</code> folder) does not exist, it will be created.<br>* If the <code>uv.lock</code> file is not present or not up to date, it will be created or updated.<br>* If the dependencies in the venv are not in <strong>sync</strong> lockfile, they will be installed.</p><p><em>The last part about <strong>syncing</strong> dependencies is the most important part, this is why the </em><code>uv sync</code><em> command is called </em><code>uv sync</code><em>. Everything else is a pre-requisite that you could do manually using other </em><code>uv</code><em> subcommands. (See </em><code>uv python -h</code><em>, </em><code>uv venv -h</code><em>, and </em><code>uv lock -h</code><em> respoectively for details.)</em></p><p><em>Syncing also means that existing dependencies will be upgraded if necessary, and extra dependencies installed in the venv but undeclared in the lockfile will be removed.</em></p></blockquote><h3>venv activation</h3><p>The way Python venvs work is that you need to <em>activate</em> them in each shell or terminal you open. Do so with the following command:</p><pre><code>source .venv/bin/activate</code></pre><p>Re-run the command if you ever open a new terminal to work on this project. Your terminal should now show the name of the active venv: <code>(alice-python)</code>.</p><p>You should be able to test your project by running either of the following commands:</p><pre><code>$ python main.py
Hello from alice-python!
$
$ python -m main
Hello from alice-python!</code></pre><h3>Better project setup</h3><p>A quite minimal Python project was created by uv, and we&#8217;ll improve it a little bit.</p><p>Did you see that the module name is just <code>main</code>? (<code>python -m main</code>?) We&#8217;ll rename it to <code>alice</code>, to make it unique. Again, you can use your own name, and you can even check <a href="https://pypi.org/">PyPI.org</a> to make sure you&#8217;re using a unique name that isn&#8217;t taken by anyone else.</p><blockquote><p><em>Actually there is already a package named </em><code>alice</code><em> on PyPI. But it&#8217;s not related to this tutorial or anything. It&#8217;s published by a guy named Walter. Anyways...</em></p></blockquote><ul><li><p>Inside the <code>alice-python</code> folder, create a subfolder named <code>alice</code> (or your name, but make it lowecase only, keeping it a valid Python module or variable name).</p></li><li><p>Then move <code>main.py</code> inside the new <code>alice</code> folder.</p></li></ul><pre><code>mkdir -p alice
mv main.py alice/</code></pre><p>Your project setup should look like this:</p><pre><code>alice-python/
&#9500;&#9472;&#9472; alice/
&#9474;   &#9492;&#9472;&#9472; main.py
&#9500;&#9472;&#9472; pyproject.toml
&#9500;&#9472;&#9472; README.md
&#9492;&#9472;&#9472; uv.lock</code></pre><p>The reason we do this instead of just renaming <code>main.py</code> to <code>alice.py</code> is that we want to prepare for the future where there will be more modules in the <code>alice</code> package.</p><p>Edit your <code>pyproject.toml</code> to make the <code>name</code> match the new name you have chosen.</p><pre><code>- name = &#8220;alice-python&#8221;
+ name = &#8220;alice&#8221;</code></pre><blockquote><p><em>Technically the </em><code>name</code><em> in the </em><code>pyproject.toml</code><em> is the only place where your chosen package name has to be unique and different from other <a href="https://pypi.org/">PyPI.org</a> packages </em>if<em> you want to publish it.</em></p><p><em>If you don&#8217;t want to publish it, it doesn&#8217;t have to be unique in any way.</em></p><p><em>Let&#8217;s assume I want to publish it but there is a package named </em><code>alice</code><em> already. I could choose </em><code>name = &#8220;alice2025&#8221;</code><em> or anything unique in the </em><code>pyproject.toml</code><em>&#8216;s </em><code>name</code><em> field, but keep the folder name simply </em><code>alice</code><em>. This way, I can still publish </em>my<em> </em><code>alice2025</code><em> package, but to use it, I can still just do </em><code>import alice</code><em>.</em></p><p><em>(If any other package such as the original </em><code>alice</code><em> package or someone else&#8217;s </em><code>alice2004</code><em> package uses the </em><code>alice</code><em> folder name to define an </em><code>alice</code><em> module, then all of these Python packages are mutually incompatible with each other.)</em></p></blockquote><p>After each change to <code>pyproject.toml</code>, it&#8217;s good practice to sync the lockfile and the dependencies again.</p><pre><code>$ uv sync
Resolved 1 package in 13ms
Audited in 0.35ms</code></pre><h3>Adding dependencies</h3><p>The last thing we&#8217;ll do in this tutorial is to split the code into separate modules, and add a fancy command line interface using <a href="https://click.palletsprojects.com/en/stable/">Click</a> and <a href="https://rich.readthedocs.io/en/stable/">Rich</a>.</p><p>Run the following to add these dependencies:</p><pre><code>uv add click rich</code></pre><p>It will print something like this, but you might get later versions:</p><pre><code>Resolved 7 packages in 22ms
Installed 5 packages in 17ms
 + click==8.3.0
 + markdown-it-py==4.0.0
 + mdurl==0.1.2
 + pygments==2.19.2
 + rich==14.2.0</code></pre><p>Note that this also added the top-level dependencies in your <code>pyproject.toml</code>:</p><pre><code>dependencies = [
    &#8220;click&gt;=8.3.0&#8221;,
    &#8220;rich&gt;=14.2.0&#8221;,
]</code></pre><p>It also locked the specific version in <code>uv.lock</code>.</p><blockquote><p><em>You can also manually edit </em><code>pyproject.toml</code><em> to add, change, upgrade, or remove dependencies. After you make any such changes, just run </em><code>uv sync</code><em> again.</em></p></blockquote><h3>Creating modules</h3><p>We&#8217;ll apply good software engineering princples and separate the core logic from the user interface.</p><p>We&#8217;ll create the following structure:</p><pre><code>alice-python/
&#9500;&#9472;&#9472; alice/
&#9474;   &#9500;&#9472;&#9472; main.py       [already exists]
&#9474;   &#9500;&#9472;&#9472; greetings.py  [new]
&#9474;   &#9492;&#9472;&#9472; cli.py        [new]
&#9500;&#9472;&#9472; pyproject.toml
&#9500;&#9472;&#9472; README.md
&#9492;&#9472;&#9472; uv.lock</code></pre><p>Our core business logic is greetings, so let&#8217;s create a file <code>alice/greetings.py</code> with the following content:</p><pre><code>from rich import print
from rich.text import Text

def hello(name: str):
    print(&#8221;Hello, &#8220;, Text(name, &#8220;bold red&#8221;), &#8220;! :hugging_face:&#8221;, sep=&#8221;&#8220;)

def goodbye(name: str):
    print(&#8221;Goodbye, &#8220;, Text(name, &#8220;italic blue&#8221;), &#8220;! :wave:&#8221;, sep=&#8221;&#8220;)</code></pre><p>Next, create the command line user interface inside <code>alice/cli.py</code>:</p><pre><code>from alice.greetings import hello, goodbye

import click
@click.group()
def cli():
    pass

@cli.command(name=&#8221;hello&#8221;)
@click.option(&#8217;--name&#8217;, prompt=&#8217;Your name&#8217;, help=&#8217;The person to greet.&#8217;)
def hello_cli(name: str):
    hello(name)

@cli.command(name=&#8221;bye&#8221;)
@click.option(&#8217;--name&#8217;, prompt=&#8217;Your name&#8217;, help=&#8217;The person to say goodbye to.&#8217;)
def bye_cli(name: str):
    goodbye(name)

if __name__ == &#8220;__main__&#8221;:
    cli()</code></pre><p>You can try out the CLI by running the following:</p><pre><code>$ python -m alice.cli hello --name Alice
Hello, Alice! &#129303;

$ python -m alice.cli bye --name Alice
Goodbye, Alice! &#128075;</code></pre><p>Thanks to the <code>rich</code> library, you&#8217;ll see emojis and colors in the output!</p><h3>Custom CLI app</h3><p>Instead of having to run <code>python -m alice.cli</code>, we want to run just a custom CLI named <code>alice</code>. We&#8217;ll implement this next.</p><p>Edit your <code>pyproject.toml</code> to add the CLI entry point and the required build backend.</p><pre><code>  [project]
  name = &#8220;alice-python&#8221;
  version = &#8220;0.1.0&#8221;
  description = &#8220;Alice&#8217;s personal Python project&#8221;
  readme = &#8220;README.md&#8221;
  requires-python = &#8220;&gt;=3.13&#8221;
  dependencies = [
      &#8220;click&gt;=8.3.0&#8221;,
      &#8220;rich&gt;=14.2.0&#8221;,
  ]
+ 
+ [project.scripts]
+ alice = &#8220;alice.cli:cli&#8221;
+ 
+ [build-system]
+ requires = [&#8221;uv_build&#8221;]
+ build-backend = &#8220;uv_build&#8221;
+ 
+ [tool.uv.build-backend]
+ namespace = true
+ module-name = &#8220;alice&#8221;
+ module-root = &#8220;&#8221;</code></pre><p>The sync the project again to install the new CLI.</p><pre><code>uv sync</code></pre><p>You can now run the CLI by simply typing <code>alice</code> in the terminal.</p><pre><code>$ alice
Usage: alice [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  bye
  hello</code></pre><p>Thanks to <code>click</code>, it shows the default help message on how to use this CLI. We can use it also in interactive mode, without specifying the name in the command.</p><pre><code>$ alice hello
Your name: World
Hello, World! &#129303;</code></pre><h3>Conclusion</h3><p><strong>Congratulations!</strong> You&#8217;ve created a simple Python project from scratch using uv.</p><p>We recommend checking out the <a href="https://docs.astral.sh/uv/">uv documentation</a> to see what else you can do with it. Happy coding!</p>]]></content:encoded></item><item><title><![CDATA[Patterns and Anti-Patterns for Building with LLMs]]></title><description><![CDATA[A bit about our guest author: Hugo Bowne- Anderson advises and teaches teams building LLM-powered systems, including engineers from Netflix, Meta, and the United Nations through my course on the AI software development lifecycle. It covers everything from retrieval and evaluation to agent design and all the steps in between. Use the code]]></description><link>https://www.marvelousmlops.io/p/patterns-and-anti-patterns-for-building</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/patterns-and-anti-patterns-for-building</guid><dc:creator><![CDATA[Hugo Bowne-Anderson]]></dc:creator><pubDate>Mon, 27 Oct 2025 10:24:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/mKTQGKIUq8M" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><p>A bit about our guest author: <a href="https://hugobowne.github.io/">Hugo Bowne- Anderson</a> advises and teaches teams building LLM-powered systems, including engineers from Netflix, Meta, and the United Nations through my course on <em><a href="https://maven.com/hugo-stefan/building-ai-apps-ds-and-swe-from-first-principles?promoCode=marvelous25">the AI software development lifecycle</a></em>. It covers everything from retrieval and evaluation to agent design and all the steps in between. Use the code <code>MARVELOUS25</code> for 25% off.</p><div><hr></div><p>In a recent <a href="https://vanishinggradients.fireside.fm/">Vanishing Gradients</a> podcast, I sat down with John Berryman, an early engineer on GitHub Copilot and author of <em>Prompt Engineering for LLMs</em>.</p><p>We framed a practical discussion around the &#8220;Seven Deadly Sins of AI App Development,&#8221; identifying common failure modes that derail projects.</p><p>For each sin, we offer a &#8220;penance&#8221;: <em><strong>a clear antidote for building more robust and reliable AI systems.</strong></em></p><p>You can also listen to this as a podcast:</p><ul><li><p><a href="https://open.spotify.com/episode/3QdTenPsyrYIrP2copuCPN?si=4O-3442TSw6hfke0-m4Frw">Spotify</a></p></li><li><p><a href="https://open.spotify.com/episode/3QdTenPsyrYIrP2copuCPN?si=4O-3442TSw6hfke0-m4Frw">Apple</a></p></li><li><p><a href="https://vanishinggradients.fireside.fm/59">Full notes and more episodes</a><br></p><div id="youtube2-mKTQGKIUq8M" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;mKTQGKIUq8M&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/mKTQGKIUq8M?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div></li></ul><p>&#128073; <em>This was a guest Q&amp;A from our July cohort of <a href="https://maven.com/hugo-stefan/building-ai-apps-ds-and-swe-from-first-principles?promoCode=marvelous25">Building AI Applications for Data Scientists and Software Engineers. Enrolment is open for our next cohort (starting November 3)</a>.</em>&#128072;</p><h3>Sin 1: Demanding 100% Accuracy</h3><p>The first sin is building an AI product with the expectation that it must be 100% accurate, especially in high-stakes domains like legal or medical documentation [<a href="https://youtu.be/mKTQGKIUq8M?t=195s">00:03:15</a>]. This mindset treats a probabilistic system like deterministic software, a mismatch that leads to unworkable project requirements and potential liability issues.</p><blockquote><p><em>The sin is requiring these AI systems to be more accurate than you would require a human to be.</em></p></blockquote><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Reframe the problem. The goal is not to replace human judgment but to save users time.</em></p></li><li><p><em>Design systems that make the AI&#8217;s work transparent, allowing users to verify the output and act as the final authority.</em></p></li><li><p><em>By setting the correct expectation, that the AI is a time-saving assistant, not an infallible oracle, you can deliver value without overpromising on reliability</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=270s">00:04:30</a>].</p></li></ul><h3>Sin 2: Granting Agents Too Much Autonomy</h3><p>This sin involves giving an agent a large, complex, closed-form task and expecting a perfect result without supervision [<a href="https://youtu.be/mKTQGKIUq8M?t=440s">00:07:20</a>]. Due to the ambiguity of language and current model limitations, the agent will likely deliver something that technically satisfies the request but violates the user&#8217;s implicit assumptions. This is particularly ineffective for tasks with a specific required solution, though it can be useful for open-ended research.</p><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Keep the user in the loop. Break down large tasks into smaller steps and build in opportunities for the agent to ask clarifying questions.</em></p></li><li><p><em>Make the agent&#8217;s process and intermediate steps visible to the user.</em></p></li><li><p><em>This collaborative approach narrows the solution space and ensures the final output aligns with the user&#8217;s actual intent</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=550s">00:09:10</a>].</p></li></ul><h3>Sin 3: Irresponsible Context Stuffing</h3><p>With ever-larger context windows, the temptation is to simply &#8220;shove it all into the prompt&#8221; and perform RAG over the full content [<a href="https://youtu.be/mKTQGKIUq8M?t=705s">00:11:45</a>]. This approach degrades model accuracy, as critical information gets lost in the noise. It also increases cost and latency without a corresponding improvement in performance.</p><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Practice responsible &#8220;context engineering.&#8221;</em></p></li><li><p><em>Instead of brute-forcing the context window, first lay out all potential information sources.</em></p></li><li><p><em>Then, create a relevance-ranking algorithm to tier the context, ensuring only the most vital information makes it into the prompt.</em></p></li><li><p><em>Build a template that fits within a sensible token budget, not the maximum window size, ensuring the final context is lean, readable, and effective</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=800s">00:13:20</a>].</p></li></ul><h3>Sin 4: Starting with Multi-Agent Complexity</h3><p>A common mistake is to jump directly to a complex, orchestrated multi-agent framework for a problem that may not require it [<a href="https://youtu.be/mKTQGKIUq8M?t=1030s">00:17:10</a>]. This introduces significant overhead, creates hard-to-debug handoffs between agents, and often results in a system that underperforms a simpler, single-agent alternative.</p><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Start with the simplest viable solution.</em></p></li><li><p><em>Build a single-prompt, single-model system first to understand its failure modes and limitations.</em></p></li><li><p><em>Only when the pain of the simple approach becomes clear should you incrementally add complexity.</em></p></li><li><p><em>This process ensures that if a multi-agent system is truly needed, its design is informed by real-world constraints rather than assumptions</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=1155s">00:19:15</a>].</p></li></ul><h3>Sin 5: Treating RAG as a Black Box</h3><p>Many developers adopt off-the-shelf RAG frameworks, treating them as a single, monolithic component [<a href="https://youtu.be/mKTQGKIUq8M?t=1245s">00:20:45</a>]. When the system fails, this black-box approach makes it nearly impossible to diagnose whether the fault lies in the data ingestion, the retrieval step, or the generation model.</p><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Build your own RAG system, and consider the individual pieces of the pipeline: indexing, query creation, retrieval, and generation.</em></p></li><li><p><em>By isolating these components, you can debug them independently.</em></p></li><li><p><em>If the generated answer is wrong, you can inspect the retrieved context to see if the issue is poor retrieval.</em></p></li><li><p><em>If the context is good but the answer is bad, the problem lies with the generation prompt or model.</em></p></li><li><p><em>This separation is key to building a reliable and maintainable system</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=1330s">00:22:10</a>].</p></li></ul><h3>Sin 6: Overloading a Single Prompt</h3><p>This sin is the smaller-scale version of giving agents too much autonomy: trying to pack too many distinct tasks into a single LLM request [<a href="https://youtu.be/mKTQGKIUq8M?t=1585s">00:26:25</a>]. Asking a model to extract multiple, unrelated pieces of information or perform several judgments at once often leads to it hallucinating, missing tasks, or producing low-quality output as its attention is divided.</p><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Break the problem into smaller, atomic pieces.</em></p></li><li><p><em>Instead of one large prompt asking for everything, use several smaller, focused prompts, which can even be run in parallel.</em></p></li><li><p><em>For example, rather than asking a model to &#8220;find all hallucinations,&#8221; iterate through each fact and ask, &#8220;Is this statement supported by the source? True or False.&#8221;</em></p></li><li><p><em>This trades a small amount of latency for a large gain in accuracy and reliability</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=1690s">00:28:10</a>].</p></li></ul><h3>Sin 7: Chasing Frameworks and Logos</h3><p>The AI ecosystem is an explosion of new tools, SDKs, and models, creating a temptation to constantly search for the perfect off-the-shelf solution [<a href="https://youtu.be/mKTQGKIUq8M?t=1755s">00:29:15</a>]. Over-indexing on third-party frameworks too early can lead to vendor lock-in, deep and opaque stack traces, and technical debt when the ecosystem inevitably shifts.</p><p><strong>The Solution/Penance:</strong></p><ul><li><p><em>Dig in and build essential components yourself, at least initially. Understand that at its core, an LLM call is just an HTTP request.</em></p></li><li><p><em>For domain-specific needs like evaluation or human review, a simple, custom-built tool is often more effective than a generic platform.</em></p></li><li><p><em>Adopt frameworks mindfully, pick one that solves a clear problem, and anticipate that you will still need to write and rewrite code as your needs evolve</em> [<a href="https://youtu.be/mKTQGKIUq8M?t=1870s">00:31:10</a>].</p></li></ul><h3>A Summary of Penances</h3><ul><li><p><strong>Accuracy:</strong> Reframe the goal. Instead of trying to make AI 100% accurate, make it a way to augment your users and save them time.</p></li><li><p><strong>Oversight:</strong> When working with complex workflows, keep the user in the loop and break down large agentic tasks into smaller, supervised steps.</p></li><li><p><strong>Context:</strong> Avoid model distraction by selectively curating context. Rank context by relevance and select only the most relevant information.</p></li><li><p><strong>Complexity:</strong> Start with the simplest possible solution before adding multi-agent complexity.</p></li><li><p><strong>RAG:</strong> In order to facilitate easy debugging, design RAG as a transparent pipeline of indexing, query generation, retrieval, and generation.</p></li><li><p><strong>Prompts:</strong> Improve accuracy by decomposing complex multi-part prompts into several prompts that are easier for the model to process.</p></li><li><p><strong>Frameworks:</strong> Build core components yourself and adopt third-party tools mindfully, anticipating change.</p></li></ul><h3>Conclusion</h3><p>The path to building reliable AI applications is paved with a deeper understanding of the models themselves.</p><p>John Berryman offers a powerful mental model:</p><blockquote><p><em>empathize with the LLM by treating it as a &#8220;super intelligent, super eager, and a forgetful AI intern&#8221; [<a href="https://youtu.be/mKTQGKIUq8M?t=2050s">00:34:10</a>]. This intern is brilliant but not psychic, gets distracted easily, and wakes up every morning (with every API call) with no memory of the past.</em></p></blockquote><p>This perspective shifts the focus from finding the perfect tool to designing robust, debuggable systems that work with the model&#8217;s nature, not against it.</p><p><em>Want to go deeper?</em> I teach <strong><a href="https://maven.com/hugo-stefan/building-ai-apps-ds-and-swe-from-first-principles?promoCode=marvelous25">a course on the entire AI software development lifecycle</a> </strong>(use <strong>code MARVELOUS25</strong> for <strong>25% off</strong>), covering everything from retrieval and evaluation to observability, agents, and production workflows. It&#8217;s designed for engineers and teams who want to move fast without getting stuck in proof of concept purgatory.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GE17!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GE17!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 424w, https://substackcdn.com/image/fetch/$s_!GE17!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 848w, https://substackcdn.com/image/fetch/$s_!GE17!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 1272w, https://substackcdn.com/image/fetch/$s_!GE17!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GE17!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png" width="1400" height="1115" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1115,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GE17!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 424w, https://substackcdn.com/image/fetch/$s_!GE17!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 848w, https://substackcdn.com/image/fetch/$s_!GE17!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 1272w, https://substackcdn.com/image/fetch/$s_!GE17!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60afa392-fd1f-47f1-b2a5-486d54ef0ea5_1400x1115.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you&#8217;d like a taste of the full course, <strong><a href="https://vanishing-gradients.kit.com/8fff155b0c">I&#8217;ve put together a free 10-part email series on building LLM-powered apps</a></strong>. It walks through practical strategies for escaping proof-of-concept purgatory: one clear, focused email at a time.</p><p><strong>Copyrights:</strong> <a href="https://hugobowne.substack.com/p/patterns-and-anti-patterns-for-building">The article was originally published on the Vanishing Gradients blog</a>.</p>]]></content:encoded></item><item><title><![CDATA[Your FREE guide to learn MLOps on Databricks]]></title><description><![CDATA[A full overview of the course]]></description><link>https://www.marvelousmlops.io/p/your-free-guide-to-learn-mlops-on</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/your-free-guide-to-learn-mlops-on</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Fri, 29 Aug 2025 13:57:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/ds__AEIqUfE" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve ever built a machine learning model in a Jupyter notebook and wondered &#8220;Now what?&#8221;,  you&#8217;re not alone. The gap between a working prototype and a production ML system is big, filled with infrastructure complexity, deployment challenges, and monitoring nightmares.</p><p>That&#8217;s why we created a <strong>free, hands-on MLOps course</strong> using <strong>Databricks Free Edition. </strong>A guide we wish we&#8217;d had when starting out with MLOps on Databricks.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>With 3 hours of video lessons and 10 detailed Substack lectures, this course takes you from zero to a production-ready ML pipeline. You&#8217;ll master automated deployment with Databricks Asset Bundles and GitHub Actions, and set up monitoring with drift detection to keep your models reliable over time.</p><p>To make things more fun, we built our use case using a toy dataset around Marvel characters, after all, we&#8217;re big fans of the universe.</p><p>The 3-hour course is now available as a single video on YouTube: </p><div id="youtube2-ds__AEIqUfE" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ds__AEIqUfE&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ds__AEIqUfE?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h2>The 10-lecture journey</h2><p>Here&#8217;s the full collection of articles so you can explore them easily:</p><p><strong>Lecture 1: Introduction to MLOps</strong></p><p>We start with the hard truth: most ML projects never make it to production. Why? Because moving from a 1000-line notebook to a scalable system requires a completely different mindset.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;005e9026-e432-4275-909f-cf4b551e5e63&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Introduction to MLOps&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:153529476,&quot;name&quot;:&quot;Maria Vechtomova&quot;,&quot;bio&quot;:&quot;Leading MLOps transformation at Ahold Delhaize | Marvelous MLOps&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F514d0746-bfdd-4609-8927-1ad7f89c728c_2832x2832.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-07-28T17:19:47.787Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/gqrl4QpfHzo&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/introduction-to-mlops&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169477215,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:26,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 2: Developing on Databricks</strong></p><p>We show why notebooks are great for exploration but become bottlenecks for MLOps. They make it difficult to write modular code, apply code quality standards, or run unit tests. We&#8217;ll show you how to use VS Code extension, Databricks CLI, and Databricks Connect to develop locally with modern engineering workflows while running PySpark code on Databricks.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;83ec3224-bd0f-4af0-9888-119a8059ab22&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Developing on Databricks&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:153529476,&quot;name&quot;:&quot;Maria Vechtomova&quot;,&quot;bio&quot;:&quot;Leading MLOps transformation at Ahold Delhaize | Marvelous MLOps&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F514d0746-bfdd-4609-8927-1ad7f89c728c_2832x2832.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-07-29T15:25:32.277Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/nEue_1THOvk&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/developing-on-databricks&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169570615,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:15,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 3: Getting started with MLflow</strong></p><p>Dive deep into the two most important MLflow classes: `mlflow.entities.Experiment` and `mlflow.entities.Run`. These form the foundation of everything else in MLflow. Learn how to set up tracking and registry URIs for Databricks, implement environment detection to distinguish local vs. Databricks environments, and handle authentication profiles for multiple developers collaborating on the same codebase.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;e7c10998-2e3b-463f-a1fa-b6e87e307236&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Getting started with MLflow&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:153529476,&quot;name&quot;:&quot;Maria Vechtomova&quot;,&quot;bio&quot;:&quot;Leading MLOps transformation at Ahold Delhaize | Marvelous MLOps&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F514d0746-bfdd-4609-8927-1ad7f89c728c_2832x2832.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-07-30T13:47:51.313Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/ze5zjnu3eGE&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/getting-started-with-mlflow&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169661225,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:8,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 4: Logging and registering models with MLflow</strong></p><p>Learn the difference between logging a pickle file and creating a proper MLflow Model. It&#8217;s the difference between a prototype and production-ready code.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;71227bf0-8b8e-4a29-bf8b-0244d85d6087&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Logging and registering models with MLflow&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:153529476,&quot;name&quot;:&quot;Maria Vechtomova&quot;,&quot;bio&quot;:&quot;Leading MLOps transformation at Ahold Delhaize | Marvelous MLOps&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F514d0746-bfdd-4609-8927-1ad7f89c728c_2832x2832.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-07-31T18:49:09.380Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/ui0gRZEcEpM&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/logging-and-registering-models-with&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169774562,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:10,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 5: Model serving architectures</strong></p><p>Not all ML model serving is created equal. We cover:</p><p>- Batch predictions (pre-compute, store, and retrieve on demand)</p><p>- Real-time serving (compute on demand)</p><p>- Hybrid approaches (feature lookup + real-time inference)</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7a36b329-ae40-4440-b959-26535c2cf96a&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Model serving architectures&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:153529476,&quot;name&quot;:&quot;Maria Vechtomova&quot;,&quot;bio&quot;:&quot;Leading MLOps transformation at Ahold Delhaize | Marvelous MLOps&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F514d0746-bfdd-4609-8927-1ad7f89c728c_2832x2832.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-01T17:39:58.369Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/Qwx1vbvYbDY&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/model-serving-architectures&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169862843,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:10,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 6: Deploying a model serving endpoint</strong></p><p>Deploy your first model endpoint with one Python command using Databricks SDK. Databricks Model Serving provides fully managed serverless model serving APIs with automatic scaling (including scale-to-zero), built-in monitoring for latency/throughput/error rates, and seamless Unity Catalog integration. Implement A/B testing with traffic splitting and model comparison strategies.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;89d93826-099e-43fd-b989-3762cc2930b0&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Deploying a model serving endpoint&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:1499488,&quot;name&quot;:&quot;Ba&#351;ak Tu&#287;&#231;e Eskili&quot;,&quot;bio&quot;:&quot;Experienced ML Engineer who designs and productionizes machine learning models, runs tests and experiments, and is passionate about automating tasks. &quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eab2c138-9f69-461b-abb6-1329326dd276_1182x924.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-02T11:00:01.186Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/Rt8qtjskuSs&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/lecture-6-deploying-model-serving&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169913939,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:6,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 7: Databricks Asset Bundles</strong></p><p>DAB provides the recommended declarative, YAML-based approach for deploying resources and dependencies on Databricks. It offers a balance between Terraform&#8217;s complexity and API flexibility. Under the hood, it leverages Terraform while managing the complexity for you. We deploy a ML workflow using DABs.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1697f683-d674-495f-bb06-13dae0bbe9e9&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Databricks Asset Bundles&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:1499488,&quot;name&quot;:&quot;Ba&#351;ak Tu&#287;&#231;e Eskili&quot;,&quot;bio&quot;:&quot;Experienced ML Engineer who designs and productionizes machine learning models, runs tests and experiments, and is passionate about automating tasks. &quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eab2c138-9f69-461b-abb6-1329326dd276_1182x924.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-03T14:50:17.308Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/iEFoGg8WMrs&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/lecture-7-databricks-asset-bundles&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:169997268,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:6,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 8: CI/CD and deployment strategies</strong></p><p>Build proper deployment pipelines with Unity Catalog governance. Learn the difference between workspace-level and catalog-level permissions (it matters more than you think). Create CI/CD pipelines for automated testing and deployment to multiple environment.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d21b0ae1-b54e-4c67-b384-15f003581375&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot; CI/CD &amp; Deployment Strategies&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:1499488,&quot;name&quot;:&quot;Ba&#351;ak Tu&#287;&#231;e Eskili&quot;,&quot;bio&quot;:&quot;Experienced ML Engineer who designs and productionizes machine learning models, runs tests and experiments, and is passionate about automating tasks. &quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eab2c138-9f69-461b-abb6-1329326dd276_1182x924.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-04T19:12:07.269Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/HwH8Q0DDDc4&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/cicd-and-deployment-strategies&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:170116050,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:8,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 9: Introduction to ML monitoring</strong></p><p>Traditional monitoring isn&#8217;t enough for ML systems. Learn the difference between data drift (input distribution shifts) and concept drift (relationship between inputs and outputs changes). Understand that not all drift is bad: sometimes models remain robust despite input changes. ML monitoring requires tracking statistical properties beyond system health, errors, and latency.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;4b8805a4-e98b-4f4f-8854-51e2dd3830a3&quot;,&quot;caption&quot;:&quot;Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Introduction to ML monitoring&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:1499488,&quot;name&quot;:&quot;Ba&#351;ak Tu&#287;&#231;e Eskili&quot;,&quot;bio&quot;:&quot;Experienced ML Engineer who designs and productionizes machine learning models, runs tests and experiments, and is passionate about automating tasks. &quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eab2c138-9f69-461b-abb6-1329326dd276_1182x924.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-05T21:56:29.291Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/ALMulduxBJY&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/introduction-to-ml-monitoring&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:170218657,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:6,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p><strong>Lecture 10: Implementing Model Monitoring on Databricks</strong></p><p>Implement end-to-end monitoring using Databricks Lakehouse Monitoring. Build four key components: inference logging (capturing model inputs/outputs), monitoring table creation, scheduled refreshes, and monitoring dashboards. Learn how Databricks automatically generates Profile Metrics Tables (summary stats, accuracy, confusion matrix) and Drift Metrics Tables for tracking data distribution changes over time.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d652ad21-16e9-492c-8dae-b5fef44b0ab9&quot;,&quot;caption&quot;:&quot;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Implementing Model Monitoring on Databricks&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:1499488,&quot;name&quot;:&quot;Ba&#351;ak Tu&#287;&#231;e Eskili&quot;,&quot;bio&quot;:&quot;Experienced ML Engineer who designs and productionizes machine learning models, runs tests and experiments, and is passionate about automating tasks. &quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eab2c138-9f69-461b-abb6-1329326dd276_1182x924.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-06T16:55:23.529Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!6TvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://marvelousmlops.substack.com/p/lecture-10-implementing-model-monitoring&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:170286459,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:8,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Marvelous MLOps Substack&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!S63_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52063997-b4b8-467e-9412-6aac8f390211_830x830.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><p>Want to learn more, go deeper, and get personalized feedback? 1st of September we start the last cohort of our MLOps with Databricks course. If you considered to join, don&#8217;t miss this opportunity! Use <a href="https://maven.com/marvelousmlops/mlops-with-databricks?promoCode=MAVEN100">this link </a>to get 100 euro off.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0QlI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0QlI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 424w, https://substackcdn.com/image/fetch/$s_!0QlI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 848w, https://substackcdn.com/image/fetch/$s_!0QlI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 1272w, https://substackcdn.com/image/fetch/$s_!0QlI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0QlI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic" width="1456" height="723" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:723,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:140791,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://marvelousmlops.substack.com/i/172164388?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0QlI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 424w, https://substackcdn.com/image/fetch/$s_!0QlI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 848w, https://substackcdn.com/image/fetch/$s_!0QlI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 1272w, https://substackcdn.com/image/fetch/$s_!0QlI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47186c08-6cbd-4133-85e2-ced1dfcec4b5_2588x1286.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We also opened enrollment for LLMOps with Databricks course starting in January 2026. Use <a href="https://maven.com/marvelousmlops/llmops-with-databricks?promoCode=EARLYBIRD">this link</a> to apply code EARLYBIRD for 20% off (valid until end of September).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qQta!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qQta!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 424w, https://substackcdn.com/image/fetch/$s_!qQta!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 848w, https://substackcdn.com/image/fetch/$s_!qQta!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 1272w, https://substackcdn.com/image/fetch/$s_!qQta!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qQta!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png" width="1456" height="667" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:667,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:597907,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://marvelousmlops.substack.com/i/172164388?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qQta!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 424w, https://substackcdn.com/image/fetch/$s_!qQta!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 848w, https://substackcdn.com/image/fetch/$s_!qQta!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 1272w, https://substackcdn.com/image/fetch/$s_!qQta!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bdb5e77-c11c-47e5-bc6e-32d6fd764884_2660x1218.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing Model Monitoring on Databricks]]></title><description><![CDATA[Lecture 10 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/lecture-10-implementing-model-monitoring</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/lecture-10-implementing-model-monitoring</guid><dc:creator><![CDATA[Başak Tuğçe Eskili]]></dc:creator><pubDate>Wed, 06 Aug 2025 16:55:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6TvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><p>Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.</p><p>This article is part of that course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>Watch <a href="https://youtu.be/pDiX5YF9kfc">the lecture</a> on YouTube.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6TvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6TvS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6TvS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6TvS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6TvS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6TvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg" width="1280" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6TvS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6TvS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6TvS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6TvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4ec3049-d50e-4eef-aae1-3cc4e2e5d448_1280x720.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>In the <a href="https://marvelousmlops.substack.com/p/introduction-to-ml-monitoring">previous lecture</a>, we covered the theory behind ML model monitoring and the tools Databricks provides for it. In this session, we&#8217;ll walk through a practical example, from inference tables to Lakehouse Monitoring implementation.</p><p><strong>Our monitoring system consists of four key components:</strong></p><ol><li><p>Inference Logging: Capturing model inputs and outputs</p></li><li><p>Monitoring Table Creation: Transforming raw logs into a format suitable for monitoring</p></li><li><p>Scheduled Refreshes: Keeping monitoring data up-to-date</p></li><li><p>Monitoring Dashboard: Visualizing metrics and detecting drift</p></li></ol><p>Let&#8217;s examine each component in detail.</p><h4>1. Inference Data Collection</h4><p><em>!Make sure that inference tables is enabled for your serving endpoint.</em></p><p>First, we need to collect data from our model serving endpoint. The notebook <em>lecture10.marvel_create_monitoring_table.py </em>demonstrates how to send requests to our endpoint and then process the logged data. In <a href="https://marvelousmlops.substack.com/p/lecture-6-deploying-model-serving">lecture 6</a>, we learned how to call the model endpoint. There are two ways to do this: either via HTTPS or by using the Workspace Client.</p><pre><code>def send_request_https(dataframe_record):
    """
    Sends a request to the model serving endpoint using HTTPS.
    """
    serving_endpoint = f"https://{os.environ['DBR_HOST']}/serving-endpoints/marvel-characters-model-serving/invocations"
    
    response = requests.post(
        serving_endpoint,
        headers={"Authorization": f"Bearer {os.environ['DBR_TOKEN']}"},
        json={"dataframe_records": dataframe_record},
    )
    return response.status_code, response.text

def send_request_workspace(dataframe_record):
    """
    Sends a request to the model serving endpoint using workspace client.
    """    
    response = workspace.serving_endpoints.query(
        name="marvel-characters-model-serving",
        dataframe_records=dataframe_record
    )
    return response</code></pre><p>We sample records from our test set and send them to the endpoint to generate some logs.</p><pre><code>
test_set = spark.table(f"{config.catalog_name}.{config.schema_name}.test_set") \
                    .withColumn("Id", col("Id").cast("string")) \
                    .toPandas()

# Sample records for testing
sampled_records = test_set[required_columns].sample(n=100, replace=True).to_dict(orient="records")
dataframe_records = [[record] for record in sampled_records]

# Test the endpoint
for i in range(len(dataframe_records)):
    status_code, response_text = send_request_https(dataframe_records[i])
    print(f"Response Status: {status_code}")
    print(f"Response Text: {response_text}")
    time.sleep(0.2)</code></pre><h4><strong>2. Monitoring Implementation</strong></h4><p><strong>2.1 Creating and Refreshing Monitoring</strong></p><p>Once logs start appearing in the inference table, we can move on to processing them into a monitoring table. For this, we&#8217;ve created a new module at <code>src/marvel_characters/monitoring.py</code>, which contains the logic for transforming inference logs into a structured monitoring table.</p><pre><code>from databricks.connect import DatabricksSession
from databricks.sdk import WorkspaceClient

from marvel_characters.config import ProjectConfig
from marvel_characters.monitoring import create_or_refresh_monitoring

spark = DatabricksSession.builder.getOrCreate()
workspace = WorkspaceClient()

# Load configuration
config = ProjectConfig.from_yaml(config_path="../project_config_marvel.yml", env="dev")

create_or_refresh_monitoring(config=config, spark=spark, workspace=workspace)</code></pre><p>This function below processes the inference data from a Delta table,<br>parses the request and response JSON fields, writes the resulting DataFrame to a Delta table for monitoring purposes.</p><pre><code>def create_or_refresh_monitoring(config: ProjectConfig, spark: SparkSession, workspace: WorkspaceClient) -&gt; None:
    """Create or refresh a monitoring table for Marvel character model serving data.

    This function processes the inference data from a Delta table,
    parses the request and response JSON fields, writes the resulting DataFrame to a Delta table for monitoring purposes.

    :param config: Configuration object containing catalog and schema names.
    :param spark: Spark session used for executing SQL queries and transformations.
    :param workspace: Workspace object used for managing quality monitors.
    """
    # Check if custom_model_payload table exists and has data
    inf_table = spark.sql(f"SELECT * FROM {config.catalog_name}.{config.schema_name}.`custom_model_payload`")
    inf_count = inf_table.count()
    logger.info(f"Found {inf_count} records in custom_model_payload table")

    if inf_count == 0:
        logger.warning("No records found in custom_model_payload table. Monitoring table will be empty.")
        return</code></pre><p>The function defines schemas for parsing the JSON request and response data:</p><pre><code>  request_schema = StructType(
        [
            StructField(
                "dataframe_records",
                ArrayType(
                    StructType(
                        [
                            StructField("Height", DoubleType(), True),
                            StructField("Weight", DoubleType(), True),
                            StructField("Universe", StringType(), True),
                            StructField("Identity", StringType(), True),
                            StructField("Gender", StringType(), True),
                            StructField("Marital_Status", StringType(), True),
                            StructField("Teams", StringType(), True),
                            StructField("Origin", StringType(), True),
                            StructField("Magic", StringType(), True),
                            StructField("Mutant", StringType(), True),
                        ]
                    )
                ),
                True,
            )
        ]
    )

    response_schema = StructType(
        [
            StructField("predictions", ArrayType(IntegerType()), True),
            StructField(
                "databricks_output",
                StructType(
                    [StructField("trace", StringType(), True), StructField("databricks_request_id", StringType(), True)]
                ),
                True,
            ),
        ]
    )</code></pre><p>It then parses the JSON data and transforms it into a structured format:</p><pre><code> inf_table_parsed = inf_table.withColumn("parsed_request", F.from_json(F.col("request"), request_schema))
    inf_table_parsed = inf_table_parsed.withColumn("parsed_response", F.from_json(F.col("response"), response_schema))
    df_exploded = inf_table_parsed.withColumn("record", F.explode(F.col("parsed_request.dataframe_records")))

    df_final = df_exploded.withColumn("timestamp_ms", (F.col("request_time").cast("long") * 1000)).select(
        F.col("request_time").alias("timestamp"),  # Use request_time as the timestamp
        F.col("timestamp_ms"),  # Select the newly created timestamp_ms column
        "databricks_request_id",
        "execution_duration_ms",
        F.col("record.Height").alias("Height"),
        F.col("record.Weight").alias("Weight"),
        F.col("record.Universe").alias("Universe"),
        F.col("record.Identity").alias("Identity"),
        F.col("record.Gender").alias("Gender"),
        F.col("record.Marital_Status").alias("Marital_Status"),
        F.col("record.Teams").alias("Teams"),
        F.col("record.Origin").alias("Origin"),
        F.col("record.Magic").alias("Magic"),
        F.col("record.Mutant").alias("Mutant"),
        F.col("parsed_response.predictions")[0].alias("prediction"),
        F.lit("marvel-characters-model-fe").alias("model_name"),
    )</code></pre><p>The function handles data quality issues and writes the processed data to a monitoring table:</p><pre><code>  # Make dropna optional if we're losing all data
    df_with_valid_values = df_final_with_status.dropna(subset=["prediction"])
    valid_count = df_with_valid_values.count()
    logger.info(f"Records with valid prediction values: {valid_count}")

    # If we lost all data after dropna, use the data before dropna
    if valid_count &gt; 0:
        df_final_with_status = df_with_valid_values
        logger.info("Using records with valid prediction values")
    else:
        logger.warning("All records have null prediction values. Using records with potential nulls.")

    # Ensure Height and Weight are properly cast to double
    df_final_with_status = df_final_with_status.withColumn("Height", F.col("Height").cast("double"))
    df_final_with_status = df_final_with_status.withColumn("Weight", F.col("Weight").cast("double"))

    # Write to the monitoring table
    df_final_with_status.write.format("delta").mode("append").saveAsTable(
        f"{config.catalog_name}.{config.schema_name}.model_monitoring"
    )</code></pre><p>Finally, it either refreshes an existing monitoring configuration or creates a new one:</p><pre><code>    try:
        workspace.quality_monitors.get(f"{config.catalog_name}.{config.schema_name}.model_monitoring")
        workspace.quality_monitors.run_refresh(
            table_name=f"{config.catalog_name}.{config.schema_name}.model_monitoring"
        )
        logger.info("Lakehouse monitoring table exist, refreshing.")
    except NotFound:
        create_monitoring_table(config=config, spark=spark, workspace=workspace)
        logger.info("Lakehouse monitoring table is created.")</code></pre><p><strong>2.2 Creating the Monitoring Table</strong></p><p>The <em>create_monitoring_table</em> function sets up a new monitoring table with Databricks&#8217; quality monitoring features:</p><pre><code>def create_monitoring_table(config: ProjectConfig, spark: SparkSession, workspace: WorkspaceClient) -&gt; None:
    """Create a new monitoring table for Marvel character model monitoring.

    This function sets up a monitoring table using the provided configuration,
    SparkSession, and workspace. It also enables Change Data Feed for the table.

    :param config: Configuration object containing catalog and schema names
    :param spark: SparkSession object for executing SQL commands
    :param workspace: Workspace object for creating quality monitors
    """
    logger.info("Creating new monitoring table..")

    monitoring_table = f"{config.catalog_name}.{config.schema_name}.model_monitoring"

    workspace.quality_monitors.create(
        table_name=monitoring_table,
        assets_dir=f"/Workspace/Shared/lakehouse_monitoring/{monitoring_table}",
        output_schema_name=f"{config.catalog_name}.{config.schema_name}",
        inference_log=MonitorInferenceLog(
            problem_type=MonitorInferenceLogProblemType.PROBLEM_TYPE_CLASSIFICATION,
            prediction_col="prediction",
            timestamp_col="timestamp",
            granularities=["30 minutes"],
            model_id_col="model_name",
        ),
    )

    # Important to update monitoring
    spark.sql(f"ALTER TABLE {monitoring_table} SET TBLPROPERTIES (delta.enableChangeDataFeed = true);")

    logger.info("Lakehouse monitoring table is created.")</code></pre><p>Once lakehouse monitoring is created, you will see 2 tables automatically created under the provided catalog and schema as well as a dashboard under dashboards tab.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jmaq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jmaq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 424w, https://substackcdn.com/image/fetch/$s_!Jmaq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 848w, https://substackcdn.com/image/fetch/$s_!Jmaq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 1272w, https://substackcdn.com/image/fetch/$s_!Jmaq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jmaq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png" width="1456" height="592" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:592,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Jmaq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 424w, https://substackcdn.com/image/fetch/$s_!Jmaq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 848w, https://substackcdn.com/image/fetch/$s_!Jmaq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 1272w, https://substackcdn.com/image/fetch/$s_!Jmaq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bffe763-fc0f-497f-9d14-1f4ef9082539_1600x651.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J2KM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J2KM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 424w, https://substackcdn.com/image/fetch/$s_!J2KM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 848w, https://substackcdn.com/image/fetch/$s_!J2KM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 1272w, https://substackcdn.com/image/fetch/$s_!J2KM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J2KM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png" width="1456" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J2KM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 424w, https://substackcdn.com/image/fetch/$s_!J2KM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 848w, https://substackcdn.com/image/fetch/$s_!J2KM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 1272w, https://substackcdn.com/image/fetch/$s_!J2KM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b2f7ac4-c5fe-46c0-9729-4b3a9b66c745_1600x1025.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This dashboard has some default panels, but you can create any panel with SQL queries.</p><h4>3. Scheduled Monitoring Refresh</h4><p>To keep our monitoring data current, we schedule regular refreshes using Databricks Asset Bundles. The configuration is defined in <em>resources/bundle_monitoring.yml:</em></p><pre><code>resources:
  jobs:
    marvel-characters-monitor-update:
      name: marvel-characters-monitor-update-workflow
      schedule:
        quartz_cron_expression: "0 0 6 ? * MON"
        timezone_id: "Europe/Amsterdam"
        pause_status: ${var.schedule_pause_status}
      tags:
        project_name: "marvel-characters"

      environments:
        - environment_key: monitor-env
          spec:
            client: "3"
            dependencies:
              - ../dist/*.whl

      tasks:
        - task_key: "refresh_monitor_table"
          environment_key: monitor-env
          spark_python_task:
            python_file: "../scripts/refresh_monitor.py"
            parameters:
              - "--root_path"
              - ${workspace.root_path}
              - "--env"
              - ${bundle.target}</code></pre><p>This job:</p><ul><li><p>Runs weekly on Mondays at 6:00 AM Amsterdam time</p></li><li><p>Executes the <em>scipts</em>/<em>refresh_monitor.py</em> script with appropriate parameters</p></li></ul><h4>4. Monitoring Refresh Script</h4><p>The scripts/refresh_monitor.py script is the entry point for our scheduled monitoring job:</p><pre><code>import argparse
from databricks.connect import DatabricksSession
from databricks.sdk import WorkspaceClient

from marvel_characters.config import ProjectConfig
from marvel_characters.monitoring import create_or_refresh_monitoring

parser = argparse.ArgumentParser()
parser.add_argument(
    "--root_path",
    action="store",
    default=None,
    type=str,
    required=True,
)

parser.add_argument(
    "--env",
    action="store",
    default=None,
    type=str,
    required=True,
)

args = parser.parse_args()
root_path = args.root_path
config_path = f"{root_path}/files/project_config_marvel.yml"

# Load configuration
config = ProjectConfig.from_yaml(config_path=config_path, env=args.env)

spark = DatabricksSession.builder.getOrCreate()
workspace = WorkspaceClient()

create_or_refresh_monitoring(config=config, spark=spark, workspace=workspace)</code></pre><p>This script:</p><ol><li><p>Parses command-line arguments for the root path and environment</p></li><li><p>Loads the appropriate configuration</p></li><li><p>Calls the monitoring refresh function</p></li></ol><h4>Conclusion</h4><p>Effective model monitoring is crucial for maintaining ML system reliability. Our implementation:</p><ol><li><p>Captures inference data from model serving endpoints</p></li><li><p>Transforms this data into a monitoring-friendly format</p></li><li><p>Schedules regular updates to keep monitoring current</p></li><li><p>Uses Databricks&#8217; built-in quality monitoring features</p></li></ol><p>By implementing this monitoring system, we can detect issues early, understand model behavior in production, and make data-driven decisions about when to retrain or update our models.</p>]]></content:encoded></item><item><title><![CDATA[Introduction to ML monitoring]]></title><description><![CDATA[Lecture 9 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/introduction-to-ml-monitoring</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/introduction-to-ml-monitoring</guid><dc:creator><![CDATA[Başak Tuğçe Eskili]]></dc:creator><pubDate>Tue, 05 Aug 2025 21:56:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/ALMulduxBJY" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced Free Edition, which opened the door for us to create a free hands-on course on MLOps with Databricks.</p><p>This article is part of that course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>In this lecture, we&#8217;ll dive into one of the most critical (and often misunderstood) aspects of production ML: monitoring.</p><p>Watch the lecture on Youtube:</p><div id="youtube2-ALMulduxBJY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ALMulduxBJY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ALMulduxBJY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>In a ML system, you need to monitor metrics that go beyond the ones you&#8217;d expect in any production system (such as system health, errors, and latency, KPIs and infrastructure costs).</p><p>In classic software, if code, data, and environment stay the same, so does behavior. ML systems are different: model performance can degrade even if nothing changes in your code or infra because ML is driven by the statistical properties of your data. User behavior can shift, seasonality or upstream data can change.</p><p>All can cause your model to underperform, even if everything else is &#8220;the same.&#8221; That&#8217;s why <strong>MLOps monitoring includes data drift, model drift, and statistical health, not just system metrics.</strong></p><p><strong>Data Drift: </strong>It happens when the distribution of the input data shifts over time, even if the relationship between inputs and outputs stays the same. For example, let&#8217;s say there is a lot of new houses entering the market in a certain district. People&#8217;s preferences, the relationship between features and price stays the same.<br>But because the model hasn&#8217;t seen enough examples of new houses, its performance drops, not because the logic changed, but because the data shifted. In this case, <strong>data drift</strong> is the root cause of model degradation.</p><p><strong>Concept Drift: </strong>It happens when the relationship between input features and the target variable changes over time so model&#8217;s original assumptions about how inputs relate to outputs no longer holds. Let&#8217;s look at housing prices example: new houses enter the market, and the government introduces a subsidy for families with children which leads to larger houses sold for lower prices. This is a shift in the underlying relationship between features like house size and the final price. Even if the input data distribution doesn&#8217;t change much, the model&#8217;s predictions will become less accurate</p><p><strong>Not all drift is bad! </strong>Sometimes, your model is robust to input changes, and performance remains stable. Check <a href="https://www.nannyml.com/blog/when-data-drift-does-not-affect-performance-machine-learning-models">this article</a> for a real example. Suppose you detect significant data drift in the &#8220;temperature&#8221; feature using Jensen-Shannon distance. But, when you check the model&#8217;s MAE (Mean Absolute Error), performance is still well within acceptable bounds. The main point is that not all drift requires action, so we need to monitor both data and performance before retraining or raising alarms.</p><p>Let&#8217;s dive into what Databricks has to offer for ML monitoring.</p><h1><strong>ML Monitoring in Databricks</strong></h1><p>Databricks Lakehouse Monitoring lets you monitor the statistical properties and quality of the data in your delta tables. We can also use it to track the performance of machine learning models and model serving endpoints by creating inference tables with model inputs and predictions.</p><p>Databricks automatically generates a few key components to help you track model and data health.</p><h2><strong>Profile Metrics Table</strong></h2><ul><li><p>Stores summary stats for each feature in each time window (count, nulls, mean, stddev, min/max, etc.).</p></li><li><p>For inference logs: also tracks accuracy, confusion matrix, F1, MSE, R&#178;, and fairness metrics.</p></li><li><p>Supports slicing/grouping (e.g., by model ID or feature value).</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-7gk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-7gk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 424w, https://substackcdn.com/image/fetch/$s_!-7gk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 848w, https://substackcdn.com/image/fetch/$s_!-7gk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 1272w, https://substackcdn.com/image/fetch/$s_!-7gk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-7gk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png" width="1400" height="989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:989,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!-7gk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 424w, https://substackcdn.com/image/fetch/$s_!-7gk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 848w, https://substackcdn.com/image/fetch/$s_!-7gk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 1272w, https://substackcdn.com/image/fetch/$s_!-7gk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bae13f-c8d5-46e7-b707-1da5a6d0d1dc_1400x989.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Drift Metrics Table</strong></h2><ul><li><p>Tracks how your data&#8217;s column distributions evolve over time using advanced drift detection techniques</p></li><li><p>Essential for identifying data quality issues, detecting shifts in real-world behavior, and ensuring that model predictions remain reliable and unbiased.</p></li></ul><p>There are two primary types of drift detection:</p><p><strong>Consecutive Drift. </strong>Compares the current time window to the previous one to detect short-term anomalies or sudden changes.<br><strong>Baseline Drift.</strong> Compares the current data to a fixed reference or baseline table, typically built from training data or a known &#8220;good&#8221; state.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HgqY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HgqY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 424w, https://substackcdn.com/image/fetch/$s_!HgqY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 848w, https://substackcdn.com/image/fetch/$s_!HgqY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!HgqY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HgqY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png" width="1400" height="1164" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1164,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!HgqY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 424w, https://substackcdn.com/image/fetch/$s_!HgqY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 848w, https://substackcdn.com/image/fetch/$s_!HgqY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!HgqY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2cd482e-ff57-4e48-897f-7a65c2243037_1400x1164.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Drift is calculated using a combination of statistical tests, distance metrics, and simple delta metrics. The table below shows which method work for which data type.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SmnS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SmnS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 424w, https://substackcdn.com/image/fetch/$s_!SmnS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 848w, https://substackcdn.com/image/fetch/$s_!SmnS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 1272w, https://substackcdn.com/image/fetch/$s_!SmnS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SmnS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png" width="1400" height="1889" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1889,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!SmnS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 424w, https://substackcdn.com/image/fetch/$s_!SmnS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 848w, https://substackcdn.com/image/fetch/$s_!SmnS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 1272w, https://substackcdn.com/image/fetch/$s_!SmnS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F572c7c74-496a-425c-b8c4-2837355e85c4_1400x1889.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For detailed guidelines, you can check these articles: <a href="https://www.nannyml.com/blog/comprehensive-guide-univariate-methods">1</a>, <a href="https://nannyml.readthedocs.io/en/stable/how_it_works/univariate_drift_comparison.html#results-summary-tldr">2</a>. Also, the <a href="https://github.com/NannyML/The-Little-Book-of-ML-Metrics">Little Book of Metrics</a> is a great resource on ML model metrics.</p><h2><strong>Inference Tables</strong></h2><p>Inference tables are a built-in feature to log model inputs and predictions from a serving endpoint directly into a Delta table in Unity Catalog. This provides a simple way to monitor, debug, and optimize models in production.</p><p>Once enabled, they automatically capture request and response payloads, as well as metadata like response time and status codes etc. It&#8217;s used for:</p><ul><li><p>Monitoring quality</p></li><li><p>Debugging</p></li><li><p>Training corpus generation</p></li></ul><p>In order to create the inference table, you need to check enable inference table box when editing a serving endpoint (can also be created programmatically). The workspace must have Unity Catalog enabled, and you&#8217;ll need the right permissions to create and manage the associated Delta table.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!imN_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!imN_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 424w, https://substackcdn.com/image/fetch/$s_!imN_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 848w, https://substackcdn.com/image/fetch/$s_!imN_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 1272w, https://substackcdn.com/image/fetch/$s_!imN_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!imN_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png" width="1400" height="708" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:708,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!imN_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 424w, https://substackcdn.com/image/fetch/$s_!imN_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 848w, https://substackcdn.com/image/fetch/$s_!imN_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 1272w, https://substackcdn.com/image/fetch/$s_!imN_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779f6fa-2e7a-4442-ac8d-c859b3944e9c_1400x708.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Inference tables log raw data. To monitor drift/performance, process them into a structured inference profile table (with timestamp, features, prediction, and optionally ground truth).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FOqG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FOqG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 424w, https://substackcdn.com/image/fetch/$s_!FOqG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 848w, https://substackcdn.com/image/fetch/$s_!FOqG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 1272w, https://substackcdn.com/image/fetch/$s_!FOqG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FOqG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png" width="1400" height="955" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:955,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!FOqG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 424w, https://substackcdn.com/image/fetch/$s_!FOqG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 848w, https://substackcdn.com/image/fetch/$s_!FOqG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 1272w, https://substackcdn.com/image/fetch/$s_!FOqG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dfa2392-b23e-4f6b-888d-14c61cf0f519_1400x955.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You can set up an automated job that processes the raw inference table into a structured monitoring table. This monitoring table would be used to</p><ul><li><p>Update generate a <strong>metrics table</strong></p></li><li><p>Automatically update the <strong>dashboard</strong></p></li></ul><p>You can also configure <strong>alerts</strong>, so you&#8217;re notified when performance drops or data shifts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MEUB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MEUB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 424w, https://substackcdn.com/image/fetch/$s_!MEUB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 848w, https://substackcdn.com/image/fetch/$s_!MEUB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 1272w, https://substackcdn.com/image/fetch/$s_!MEUB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MEUB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png" width="1004" height="412" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d741b18e-2501-4507-9db3-7f036a240f90_1004x412.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:412,&quot;width&quot;:1004,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!MEUB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 424w, https://substackcdn.com/image/fetch/$s_!MEUB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 848w, https://substackcdn.com/image/fetch/$s_!MEUB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 1272w, https://substackcdn.com/image/fetch/$s_!MEUB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd741b18e-2501-4507-9db3-7f036a240f90_1004x412.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Monitoring pipeline</strong></h2><p>Let&#8217;s take a look at a model serving use case, similar to what we cover in the course. For example, there is a dynamic pricing model that gets retrained weekly, and the updated model is deployed to the serving endpoint. The workflow is similar to what we covered in <a href="https://marvelousmlops.substack.com/p/lecture-7-databricks-asset-bundles">Lecture 7</a>.</p><p>A separate workflow would be required to update the Lakehouse monitor when ground truth labels arrive. We&#8217;ll go through it in our final lecture.</p><p>In the end, we&#8217;ll have two workflows, one for training and deployment, and the other one &#8212; for monitoring:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nnue!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nnue!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 424w, https://substackcdn.com/image/fetch/$s_!nnue!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 848w, https://substackcdn.com/image/fetch/$s_!nnue!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 1272w, https://substackcdn.com/image/fetch/$s_!nnue!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nnue!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png" width="1400" height="375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:375,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!nnue!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 424w, https://substackcdn.com/image/fetch/$s_!nnue!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 848w, https://substackcdn.com/image/fetch/$s_!nnue!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 1272w, https://substackcdn.com/image/fetch/$s_!nnue!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64402f4f-0c0a-4d38-9323-e45d60ca84b8_1400x375.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Conclusions</strong></h2><p>Here are some key takeaways from this lecture:</p><ul><li><p>ML monitoring goes beyond system metrics, it includes data quality, drift, and model performance.</p></li><li><p><a href="https://docs.databricks.com/aws/en/lakehouse-monitoring/">Databricks Lakehouse Monitoring</a> provides built-in tools for tracking data and model health over time.</p></li><li><p>Not all data drift is bad &#8212; always check model performance before reacting.</p></li><li><p>Inference tables + monitoring pipelines enable end-to-end visibility and alerting in production.</p></li></ul><p>In the next lecture, we&#8217;ll demonstrate how to create a Lakehouse monitor on Databricks, and deploy the monitoring pipeline.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[ CI/CD & Deployment Strategies]]></title><description><![CDATA[Lecture 8 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/cicd-and-deployment-strategies</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/cicd-and-deployment-strategies</guid><dc:creator><![CDATA[Başak Tuğçe Eskili]]></dc:creator><pubDate>Mon, 04 Aug 2025 19:12:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/HwH8Q0DDDc4" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on MLOps with Databricks.</p><p>This article is part of that course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>Watch lecture 8 on Youtube:</p><div id="youtube2-HwH8Q0DDDc4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;HwH8Q0DDDc4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/HwH8Q0DDDc4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>In this lecture, we&#8217;ll explore how to structure your data and assets for robust, secure, and scalable machine learning operations on Databricks, and how to automate deployments using CI/CD pipelines.</p><div><hr></div><h1><strong>Unity Catalog, Workspaces, and Data Organization</strong></h1><p>We&#8217;ve already interacted with Unity Catalog, using it to create delta tables and register models. For a workspace to use Unity Catalog, it must be attached to a Unity Catalog metastore, which is the top-level container for all data and AI asset metadata.</p><p>You can only have one metastore per cloud region, and each workspace can only be attached to one metastore in that region.</p><p>Unity Catalog organizes assets in a three-tier hierarchy:</p><ul><li><p>Catalogs (e.g., mlops_dev, mlops_acc, mlops_prd)</p></li><li><p>Schemas within catalogs (in our case, we have the same schema name in each catalog, marvel_characters)</p></li><li><p>Assets within schemas (tables, views, models, etc.)</p></li></ul><p>Assets are referenced using a three-part naming convention: <em>catalog.schema.asset.</em></p><h1><strong>Access Control: Securables and Permissions</strong></h1><p>In Databricks, permissions can be set on the workspace and on Unity Catalog level.</p><p><strong>Workspace-level securables:</strong> Notebooks, clusters, jobs &#8212; accessed via ACLs.</p><p><strong>Unity Catalog-level securables</strong>: Tables, schemas, models &#8212; accessed via metastore-level privileges.</p><p><strong>Workspace binding and access modes:</strong> if the catalog has OPEN mode, it can be accessed from any workspace. Use ISOLATED mode to control cross-project access.</p><p>In a typical setup, an ML project or team has a set of Databricks workspaces (dev, acc, and prd), and set of catalogs or schemas within a larger catalog.</p><p>In the course example, for simplicity we use a shared workspace for development, acceptance and production. But we have a dedicated catalog for dev, acc, and prd, with limited permissions, so that we can ensure things do not get broken unintentionally.</p><p><strong>For an ideal setup, you need to follow these rules:</strong></p><ul><li><p>All ML pipelines form all workspaces (dev, acc, prd) have read access to production data (e.g., prd_gold), ensuring consistency.</p></li><li><p>From each workspace, the data can only be wtitten to its own catalog.</p></li><li><p>Users only have direct access to the dev workspace; deployments to acc/prd must go through CI/CD pipelines, using service principals for security and traceability.</p></li></ul><p>In the example below, the hotel booking team and dev SPN have &#8220;write&#8221; permissions to mlops_dev.hotel_booking, and &#8220;read&#8221; permissions on prd_gold.hotel_booking (production data delivered by the data engineering team), and the hotel_booking schema from mlops_acc and mlops_prd catalogs.</p><p>Service principals (SPNs) have scoped access, only operating within their intended workspace/catalog</p><h1><strong>Branching and Release Strategy</strong></h1><p>In the course, we use a version of Git Flow:</p><ul><li><p>Feature branches are created from main.</p></li><li><p>Developers open PRs to main, triggering the CI pipeline.</p></li><li><p>CI runs pre-commit checks, unit tests, and version checks.</p></li><li><p>At least 2 approvals are required to merge (enforced via branch protection rules).</p></li><li><p>Direct pushes to main are not allowed.</p></li></ul><p>Once merged, the CD pipeline deploys to acceptance and production, using environment-scoped secrets and SPNs. Production deployment should be protected by deployment protection rules (only deployed after approval).</p><h1><strong>CI/CD in Action</strong></h1><p>Let&#8217;s look at how this is implemented in our Marvelous MLOps codebase.</p><p><strong>CI Pipeline: .github/workflows/ci.yml : </strong>This pipeline is triggered on every push or PR to main or dev:</p><pre><code>name: CI

on:
  pull_request:
    branches:
      - main

jobs:
  pytest_and_checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
        with:
          # Fetch all history for all branches and tags
          fetch-depth: 0
          ref: ${{ github.head_ref }}

      - name: Git tag from version.txt
        run: |
          echo "VERSION=$(cat version.txt)"
          git tag $VERSION

      - name: Install uv
        uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 #v5.4.1

      - name: Install the dependencies
        run: |
          uv sync --extra test

      - name: Run pre-commit checks
        run: |
          uv run pre-commit run --all-files

      - name: run pytest
        run: |
          uv run pytest -m "not ci_exclude"
</code></pre><p>What it does:</p><ul><li><p>Runs on PRs and pushes to main/dev</p></li><li><p>Installs dependencies, runs linting and tests</p></li><li><p>Checks that the version is unique (to prevent accidental duplicate releases)</p></li></ul><p><strong>CD Pipeline: .github/workflows/cd.yml</strong></p><p>This pipeline is triggered after a successful merge to main:</p><pre><code>name: CD

on:
  workflow_dispatch:

  push:
    branches:
        - 'main'

jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        environment: [acc, prd]
    environment: ${{ matrix.environment }}
    permissions:
      contents: write # to push tag
    env:
      DATABRICKS_HOST: ${{ vars.DATABRICKS_HOST }}
      DATABRICKS_CLIENT_ID: ${{ secrets.DATABRICKS_CLIENT_ID }}
      DATABRICKS_CLIENT_SECRET: ${{ secrets.DATABRICKS_CLIENT_SECRET }}
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2

      - name: Install Databricks CLI
        uses: databricks/setup-cli@49580195afe1ccb06d195764a1d0ae9fabfe2edd #v0.246.0
        with:
          version: 0.246.0

      - name: Configure Databricks CLI
        run: |
          mkdir -p ~/.databricks
          cat &gt; ~/.databrickscfg &lt;&lt; EOF
          [marvelous]
          host = ${{ vars.DATABRICKS_HOST }}
          client_id = ${{ secrets.DATABRICKS_CLIENT_ID }}
          client_secret = ${{ secrets.DATABRICKS_CLIENT_SECRET }}
          EOF

      - name: Install uv
        uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 #v5.4.1

      - name: Deploy to Databricks
        env:
          DATABRICKS_BUNDLE_ENV: ${{ matrix.environment }}
        run: |
            databricks bundle deploy \
              --var="git_sha=${{ github.sha }}" \
              --var="branch=${{ github.ref_name }}"
            if [ "${{ matrix.environment }}" = "prd" ]; then
            echo "VERSION=$(cat version.txt)"
            git tag $VERSION
            git push origin $VERSION
            fi</code></pre><p>What it does:</p><ul><li><p>Only runs on push to main</p></li><li><p>Builds the wheel (databricks bundle deploy takes care of that)</p></li><li><p>Deploys the Lakeflow job to both acceptance and production using environment-specific secrets</p></li><li><p>Uses the Databricks CLI to deploy bundles</p></li></ul><h1><strong>Setting Up Service Principals (SPNs) for Secure CI/CD</strong></h1><p>For your CD pipeline to deploy to Databricks automatically and securely, you must use a Service Principal (SPN). This ensures that all deployments are performed by a dedicated identity with tightly scoped permissions, rather than by personal user credentials.</p><p>A Service Principal is a special, non-human identity used by applications, automation tools, or CI/CD pipelines to authenticate and interact with cloud resources securely.</p><h2><strong>Why use an SPN instead of a user account?</strong></h2><ul><li><p>Security: SPNs are not tied to any individual, so if someone leaves the team, you don&#8217;t risk losing access or exposing credentials.</p></li><li><p>Least privilege: SPNs can be granted only the permissions they need for deployment &#8212; nothing more.</p></li><li><p>Auditability: All actions performed by the CI/CD pipeline are clearly attributable to the SPN, making it easy to track changes and meet compliance requirements.</p></li><li><p>Automation: SPNs enable fully automated, hands-off deployments, since their credentials (client ID and secret) can be securely stored in your CI/CD system (like GitHub Actions).</p></li></ul><h2><strong>Configuring SPNs</strong></h2><ol><li><p><strong>Create a Service Principal in Databricks:</strong></p></li></ol><ul><li><p>Go to the Databricks workspace admin console.</p></li><li><p>Navigate to User Management &#8594; Service Principals.</p></li><li><p>Click Add Service Principal and follow the prompts.</p></li><li><p>Note the Client ID and Client Secret that are generated.</p></li></ul><p>Assign Permissions to the SPN:</p><ul><li><p>Grant the SPN the necessary privileges in Unity Catalog, jobs, and workspace resources (e.g., CAN_MANAGE or CAN_RUN on the relevant schemas, jobs, and endpoints).</p></li><li><p>Make sure the SPN has access only to the environments it should deploy to (e.g., acc and prd).</p></li></ul><p><strong>2. Add SPN Credentials to GitHub Actions (or your CI/CD system)</strong></p><p>In your GitHub repository, go to Settings &#8594; Environments. Create environments &#8220;prd&#8221; and &#8220;acc&#8221;.</p><p>Add the following Environment secrets:</p><ul><li><p><em><strong>DATABRICKS_CLIENT_ID (the SPN&#8217;s client ID)</strong></em></p></li><li><p><em><strong>DATABRICKS_CLIENT_SECRET (the SPN&#8217;s client secret)</strong></em></p></li></ul><p>Add the following Environment variable:</p><ul><li><p><em><strong>DATABRICKS_HOST (your Databricks workspace URL)</strong></em></p></li></ul><p>These secrets and variables will be available to your GitHub Actions workflows and can be referenced as environment variables.</p><p>In your <em><strong>.github/workflows/cd.yml</strong></em>, we are referencing these secrets and variable in the env block for deployment steps. We also set the environment using matrix.</p><pre><code>strategy:
      matrix:
        environment: [acc, prd]
environment: ${{ matrix.environment }}
env:
  DATABRICKS_HOST: ${{ vars.DATABRICKS_HOST }}
  DATABRICKS_CLIENT_ID: ${{ secrets.DATABRICKS_CLIENT_ID }}
  DATABRICKS_CLIENT_SECRET: ${{ secrets.DATABRICKS_CLIENT_SECRET }}</code></pre><p>The Databricks CLI and bundle deployment commands will pick up these variables and authenticate as the SPN.</p><h2><strong>Bonus: Authenticating to Serverless Endpoints with Service Principals</strong></h2><p>In Lecture 6, we showed how to send requests to a Databricks Serverless endpoint using a Personal Access Token (PAT).</p><p>However, for production systems and service-to-service communication, Service Principals (SPNs) provide a more secure and scalable authentication method. <br>Step 1: Grant Permissions to Your SPN</p><p>Before making requests, ensure your Service Principal has CAN_QUERY permission on the model serving endpoint:</p><ol><li><p>Navigate to your endpoint in the Databricks UI</p></li><li><p>Click on &#8220;Permissions&#8221;</p></li><li><p>Add your Service Principal with &#8220;Can Query&#8221; permission</p></li></ol><p>Step 2: Generate an OAuth Token Using SPN Credentials</p><pre><code>import os
import requests

from requests.auth import HTTPBasicAuth

def get_token():
   response = requests.post(
       f"https://{os.environ['DBR_HOST']}/oidc/v1/token",
       auth=HTTPBasicAuth(
           os.environ["DATABRICKS_CLIENT_ID"],
           os.environ["DATABRICKS_CLIENT_SECRET"]
       ),
       data={
           'grant_type': 'client_credentials',
           'scope': 'all-apis'
       }
   )
   return response.json()["access_token"]

os.environ['DBR_TOKEN'] = get_token()</code></pre><p>Step 3: Use the Token to Call Your Model Endpoint</p><p>Now you can use this token with the same endpoint invocation code from Lecture 6, replacing the authentication method:</p><pre><code>def call_endpoint(record):
    """
    Calls the model serving endpoint with a given input record.
    """
    # Ensure the host URL is complete with domain suffix (.com, etc.)
    host = os.environ['DBR_HOST']
    # If the host doesn't contain a dot, it's likely missing the domain suffix
    if '.' not in host:
        print(f"Warning: DBR_HOST '{host}' may be incomplete. Adding '.com' domain suffix.")
        host = f"{host}.com"
        
    serving_endpoint = f"https://{host}/serving-endpoints/marvel-character-model-serving/invocations"
    
    print(f"Calling endpoint: {serving_endpoint}")
    
    response = requests.post(
        serving_endpoint,
        headers={"Authorization": f"Bearer {os.environ['DBR_TOKEN']}"},
        json={"dataframe_records": record},
    )
    return response.status_code, response.text</code></pre><h1><strong>Key Takeaways</strong></h1><p>In this lecture, we discussed and showed how:</p><ul><li><p>Catalogs, schemas, and workspaces provide clean separation and access control.</p></li><li><p>Service principals ensure automation is secure and scoped.</p></li><li><p>Git flow and branch protection rules enforce code quality and review.</p></li><li><p>CI/CD pipelines automate validation and deployment, with no manual pushes to production.</p></li></ul><p>In the next lecture, we&#8217;ll talk about monitoring. Because machine learning models only start living once they are deployed, and there is no MLOps without proper monitoring.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Databricks Asset Bundles]]></title><description><![CDATA[Lecture 7 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/lecture-7-databricks-asset-bundles</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/lecture-7-databricks-asset-bundles</guid><dc:creator><![CDATA[Başak Tuğçe Eskili]]></dc:creator><pubDate>Sun, 03 Aug 2025 14:50:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/iEFoGg8WMrs" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on MLOps with Databricks.</p><p>This article is part of that course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>In this lecture, we&#8217;ll focus on how to automate and the entire ML workflow using DAB. You can also follow along with the full walkthrough on the Marvelous MLOps YouTube channel:</p><div id="youtube2-iEFoGg8WMrs" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;iEFoGg8WMrs&quot;,&quot;startTime&quot;:&quot;7s&quot;,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/iEFoGg8WMrs?start=7s&amp;rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>All code covered in this repository is available <a href="https://github.com/marvelousmlops/marvel-characters/tree/main">here</a>.</p><div><hr></div><h4>Why Databricks Asset Bundles?</h4><p>When deploying resources and their dependencies on Databricks, you have a few options:</p><ul><li><p>Terraform: Full infrastructure-as-code control, but can be complex.</p></li><li><p>Databricks APIs: Flexible, but requires custom scripting.</p></li><li><p>Databricks Asset Bundles (DAB): The recommended, declarative, YAML-based approach.</p></li></ul><p>DAB offers a balance between simplicity and power. Under the hood, it leverages Terraform, so you get all the benefits of infrastructure-as-code, without having to manage raw Terraform code yourself. This is ideal for teams looking to standardize and automate job deployments in a scalable, maintainable way.</p><h4>What is DAB?</h4><p>Databricks Asset Bundle (DAB) is the way to package your code, jobs, configuration, and dependencies together in a structured, version-controlled format. With DAB, you define jobs, notebooks, models, and their dependencies using YAML files.</p><p><strong>Key features:</strong></p><ul><li><p>Declarative YAML configuration: Define everything in one place.</p></li><li><p>Multi-environment support: Easily target dev, staging, prod, etc.</p></li><li><p>CI/CD friendly: Fits naturally into automated pipelines.</p></li><li><p>Version-controlled: All changes are tracked in your repo.</p></li></ul><h4>What is a Lekeflow job?</h4><p>Lakeflow Jobs (previously Databricks workflows) provide the execution and orchestration layer. Workflows let you run tasks (notebooks, scripts, SQL) on a schedule or in response to events, with support for dependencies, retries, parameter passing, and alerts.</p><h3>Machine learning pipeline</h3><p>Here&#8217;s the workflow we&#8217;ll create with DAB:</p><ol><li><p><strong>Preprocessing.</strong> Runs a data processing script <em>scripts/process_data.py</em>.</p></li><li><p><strong>Train &amp; Evaluate. </strong>Trains and evaluates the model using <em>scripts/train_register_custom_model.py</em>.</p></li><li><p><strong>Model Update.</strong> Conditional step: if the new model is better, release a flag and register it.</p></li><li><p><strong>Deployment. </strong>Deploy the registered model by creating or updating a serving endpoint using <em>scripts/deploy_model.py</em>.</p></li></ol><p>The scripts we are running in each step can be found in the scripts folder in our <a href="https://github.com/marvelousmlops/marvel-characters/tree/main/scripts">repo</a>. Parameters are passed between steps, and are explicitly defined in our databricks.yml file.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UD3L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UD3L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 424w, https://substackcdn.com/image/fetch/$s_!UD3L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 848w, https://substackcdn.com/image/fetch/$s_!UD3L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 1272w, https://substackcdn.com/image/fetch/$s_!UD3L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UD3L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png" width="1456" height="563" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:563,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UD3L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 424w, https://substackcdn.com/image/fetch/$s_!UD3L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 848w, https://substackcdn.com/image/fetch/$s_!UD3L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 1272w, https://substackcdn.com/image/fetch/$s_!UD3L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F364dffc2-759c-4dce-a585-556ea2d42e78_1582x612.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example workflow for marvel characters use case.</figcaption></figure></div><h4>databricks.yml file</h4><p>The bundle configuration is defined in the <strong>databricks.yml. </strong>The minimal configuration only contains the bundle name and the target. Let&#8217;s look at the databricks.yml file from our project:</p><pre><code>bundle:
  name: marvel-characters

include:
  - resources/*

artifacts:
  default:
    type: whl
    build: uv build
    path: .

variables:
  git_sha:
    description: git_sha
    default: abcd
  branch:
    description: branch
    default: main
  schedule_pause_status:
    description: schedule pause status
    default: PAUSED


targets:
  dev:
    default: true
    mode: development
    workspace:
      host: &lt;your host&gt;
      root_path: /Workspace/Users/${workspace.current_user.userName}/.bundle/${bundle.target}/${bundle.name}
      profile: marvelous
    variables:
      schedule_pause_status: PAUSED

  acc:
    presets:
      name_prefix: 'acc_'
    workspace:
      host: &lt;your host&gt;
      root_path: /Shared/.bundle/${bundle.target}/${bundle.name}
      profile: marvelous
    variables:
      schedule_pause_status: PAUSED

  prd:
    mode: production
    workspace:
      host: &lt;your host&gt;
      root_path: /Shared/.bundle/${bundle.target}/${bundle.name}
      profile: marvelous
    variables:
      schedule_pause_status: PAUSED # normally UNPAUSED</code></pre><p>The databricks.yml file here does not contain any resources. All the resources are defined under the resource folder as separate .yml files. The resource configurations are included in the deployment thanks to <strong>the include section</strong> in the main configuration file.</p><p><strong>The artifacts section</strong> defines how the code is packaged. The packaged code is referenced in the resources.</p><p><strong>The variables section </strong>defines shared variables (git SHA, branch, schedule status), which can be defined per target or passed to the deployment at the deploy step.</p><p><strong>The targets section </strong>defines deployment targets, which refer to separate environments (dev, acc, prd), each with its own settings.</p><h4>Machine learning pipeline: <strong>model_deployment.yml</strong></h4><p>The <strong>resources/model_deployment.yml </strong>file defines the actual ML workflow as a Databricks job, with tasks and dependencies.</p><p>The file contains one job with a schedule that will be paused based on the variable value schedule_pause_status. We want to schedule the job in production, but pause it for acceptance target (which does not have development mode, hence will not be paused by default).</p><p>The job will run using serverless environment 3, which we discussed earlier in <a href="https://marvelousmlops.substack.com/p/developing-on-databricks">lecture 2</a>.</p><pre><code>resources:
  jobs:
    deployment:
      name: ${bundle.name}-workflow
      schedule:
        quartz_cron_expression: "0 0 6 ? * MON"
        timezone_id: "Europe/Amsterdam"
        pause_status: ${var.schedule_pause_status}
      tags:
        project_name: "marvel-characters"

      environments:
        - environment_key: default
          spec:
            client: "3"
            dependencies:
              - ../dist/*.whl

      tasks:
        - task_key: "preprocessing"
          environment_key: default
          spark_python_task:
            python_file: "../scripts/process_data.py"
            parameters:
              - "--root_path"
              - "${workspace.root_path}"
              - "--env"
              - "${bundle.target}"

        - task_key: "train_model"
          environment_key: default
          depends_on:
            - task_key: "preprocessing"
          spark_python_task:
            python_file: "../scripts/train_register_custom_model.py"
            parameters:
              - "--root_path"
              - "${workspace.root_path}"
              - "--env"
              - "${bundle.target}"
              - "--git_sha"
              - "${var.git_sha}"
              - "--job_run_id"
              - "{{job.run_id}}"
              - "--branch"
              - "${var.branch}"

        - task_key: model_updated
          condition_task:
            op: "EQUAL_TO"
            left: "{{tasks.train_model.values.model_updated}}"
            right: "1"
          depends_on:
            - task_key: "train_model"

        - task_key: "deploy_model"
          environment_key: default
          depends_on:
            - task_key: "model_updated"
              outcome: "true"
          spark_python_task:
            python_file: "../scripts/deploy_model.py"
            parameters:
              - "--root_path"
              - "${workspace.root_path}"
              - "--env"
              - "${bundle.target}"</code></pre><p>The job consists of tasks: preprocessing, training, model update check, deployment. We pass parameters between the tasks using DABs, and also pass specific Python parameters to the tasks, such as the job run id, and git_sha, which allows us to tag MLflow runs and registered models with the actual code version.</p><p>Let&#8217;s take a closer look at each of the tasks.</p><h4>Preprocessing Script: scripts/process_data.py</h4><p>The preprocessing script handles data loading and preprocessing, similar to what we have done in <a href="https://marvelousmlops.substack.com/p/developing-on-databricks">lecture 2</a>. We are using DataProcessor class from our custom package, which can be found under <em><a href="https://github.com/marvelousmlops/marvel-characters/blob/main/src/marvel_characters/data_processor.py">src</a></em> folder.</p><pre><code>import argparse
import yaml
from loguru import logger
from pyspark.sql import SparkSession
import pandas as pd

from marvel_characters.config import ProjectConfig
from marvel_characters.data_processor import DataProcessor

parser = argparse.ArgumentParser()
parser.add_argument(
    "--root_path",
    action="store",
    default=None,
    type=str,
    required=True,
)

parser.add_argument(
    "--env",
    action="store",
    default=None,
    type=str,
    required=True,
)


args = parser.parse_args()
config_path = f"{args.root_path}/files/project_config_marvel.yml"

config = ProjectConfig.from_yaml(config_path=config_path, env=args.env)

logger.info("Configuration loaded:")
logger.info(yaml.dump(config, default_flow_style=False))

# Load the Marvel characters dataset
spark = SparkSession.builder.getOrCreate()

# Example: Adjust the path and loading logic as per your Marvel dataset location
filepath = f"{args.root_path}/files/data/marvel_characters_dataset.csv"

# Load the data
df = pd.read_csv(filepath)

# If you have Marvel-specific synthetic/test data generation, use them here.
# Otherwise, just use the loaded Marvel dataset as is.
logger.info("Marvel data loaded for processing.")

# Initialize DataProcessor
data_processor = DataProcessor(df, config, spark)

# Preprocess the data
data_processor.preprocess()

# Split the data
X_train, X_test = data_processor.split_data()
logger.info("Training set shape: %s", X_train.shape)
logger.info("Test set shape: %s", X_test.shape)

# Save to catalog
logger.info("Saving data to catalog")
data_processor.save_to_catalog(X_train, X_test)</code></pre><h4>Training &amp; Registration Script: scripts/train_register_custom_model.py</h4><p>This script trains, evaluates, and registers the model, if the model is improved. We are using <em><a href="https://github.com/marvelousmlops/marvel-characters/blob/main/src/marvel_characters/models/custom_model.py">custom model class</a></em> which we created in <a href="https://marvelousmlops.substack.com/p/logging-and-registering-models-with">lecture 4</a>.</p><pre><code>import argparse

import mlflow
from loguru import logger
from pyspark.dbutils import DBUtils
from pyspark.sql import SparkSession
from importlib.metadata import version

from marvel_characters.config import ProjectConfig, Tags
from marvel_characters.models.basic_model import BasicModel
from marvel_characters.models.custom_model import MarvelModelWrapper

parser = argparse.ArgumentParser()
parser.add_argument(
    "--root_path",
    action="store",
    default=None,
    type=str,
    required=True,
)

parser.add_argument(
    "--env",
    action="store",
    default=None,
    type=str,
    required=True,
)
parser.add_argument("--git_sha", type=str, required=True, help="git sha of the commit")
parser.add_argument("--job_run_id", type=str, required=True, help="run id of the run of the databricks job")
parser.add_argument("--branch", type=str, required=True, help="branch of the project")

args = parser.parse_args()
root_path = args.root_path
config_path = f"{root_path}/files/project_config_marvel.yml"

config = ProjectConfig.from_yaml(config_path=config_path, env=args.env)
spark = SparkSession.builder.getOrCreate()
dbutils = DBUtils(spark)
tags_dict = {"git_sha": args.git_sha, "branch": args.branch, "job_run_id": args.job_run_id}
tags = Tags(**tags_dict)

# Initialize Marvel custom model
basic_model = BasicModel(config=config, tags=tags, spark=spark)
logger.info("Marvel BasicModel initialized.")

# Load Marvel data
basic_model.load_data()
logger.info("Marvel data loaded.")

# Prepare features
basic_model.prepare_features()

# Train the Marvel model
basic_model.train()
logger.info("Marvel model training completed.")

# Train the Marvel model
basic_model.log_model()

# Evaluate Marvel model
model_improved = basic_model.model_improved()
logger.info("Marvel model evaluation completed, model improved: %s", model_improved)

if model_improved:
    # Register the model
    basic_model.register_model()
    marvel_characters_v = version("marvel_characters")

    pyfunc_model_name = f"{config.catalog_name}.{config.schema_name}.marvel_character_model_custom"
    code_paths=[f"{root_path}/artifacts/.internal/marvel_characters-{marvel_characters_v}-py3-none-any.whl"]

    wrapper = MarvelModelWrapper()
    latest_version = wrapper.log_register_model(wrapped_model_uri=f"{basic_model.model_info.model_uri}",
                            pyfunc_model_name=pyfunc_model_name,
                            experiment_name=config.experiment_name_custom,
                            input_example=basic_model.X_test[0:1],
                            tags=tags,
                            code_paths=code_paths)

    logger.info("New model registered with version:", latest_version)
    dbutils.jobs.taskValues.set(key="model_version", value=latest_version)
    dbutils.jobs.taskValues.set(key="model_updated", value=1)

else:
    dbutils.jobs.taskValues.set(key="model_updated", value=0)</code></pre><h4>Deployment Script: scripts/deploy_model.py</h4><p>The deployment script deploys the latest registered model to a serving endpoint. We covered the logics in l<a href="https://marvelousmlops.substack.com/p/lecture-6-deploying-model-serving">ecture 6</a>.</p><pre><code>import argparse

from loguru import logger
from pyspark.dbutils import DBUtils
from pyspark.sql import SparkSession

from marvel_characters.config import ProjectConfig
from marvel_characters.serving.model_serving import ModelServing

parser = argparse.ArgumentParser()
parser.add_argument(
    "--root_path",
    action="store",
    default=None,
    type=str,
    required=True,
)

parser.add_argument(
    "--env",
    action="store",
    default=None,
    type=str,
    required=True,
)

args = parser.parse_args()

config_path = f"{args.root_path}/files/project_config_marvel.yml"

spark = SparkSession.builder.getOrCreate()
dbutils = DBUtils(spark)
model_version = dbutils.jobs.taskValues.get(taskKey="train_model", key="model_version")

# Load project config
config = ProjectConfig.from_yaml(config_path=config_path, env=args.env)
logger.info("Loaded config file.")

catalog_name = config.catalog_name
schema_name = config.schema_name
endpoint_name = f"marvel-characters-model-serving-{args.env}"
endpoint_name = "marvel-character-model-serving"

# Initialize Marvel Model Serving Manager
model_serving = ModelServing(
    model_name=f"{catalog_name}.{schema_name}.marvel_character_model_custom",
    endpoint_name=endpoint_name
)

# Deploy the Marvel model serving endpoint
model_serving.deploy_or_update_serving_endpoint(version=model_version)
logger.info("Started deployment/update of the Marvel serving endpoint.")</code></pre><h3>Running and Managing Bundles</h3><p>We use Databricks CLI commands to validate, deploy and run our workflow.</p><ul><li><p><em>databricks bundle validate</em>&#8202;&#8212;&#8202;validate your bundle</p></li><li><p><em>databricks bundle deploy&#8202;</em>&#8212;&#8202;deploys your bundle</p></li><li><p><em>databricks bundle run</em>&#8202;&#8212;&#8202;run the job</p></li><li><p><em>databricks bundle destroy&#8202;</em>&#8212;&#8202;Tear down resources</p></li></ul><p>Once we deploy our bundle, we see our workflow created in the target workspace.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-FsE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-FsE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 424w, https://substackcdn.com/image/fetch/$s_!-FsE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 848w, https://substackcdn.com/image/fetch/$s_!-FsE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 1272w, https://substackcdn.com/image/fetch/$s_!-FsE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-FsE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png" width="1456" height="573" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed72694c-2965-45f0-8611-47390cd3644d_1600x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:573,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-FsE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 424w, https://substackcdn.com/image/fetch/$s_!-FsE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 848w, https://substackcdn.com/image/fetch/$s_!-FsE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 1272w, https://substackcdn.com/image/fetch/$s_!-FsE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed72694c-2965-45f0-8611-47390cd3644d_1600x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Conclusion</h3><p>With Databricks Asset Bundles, we can package all logic, dependencies, and configurations together, define robust multi&#8209;step ML workflows, and version and automate deployments across environments. This enables true reproducibility and aligns with CI/CD best practices, making deployments consistent and reliable.</p><p>In the next lecture, we will explore how to deploy a bundle through a CI/CD pipeline to fully automate the path from development to production.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Deploying a model serving endpoint]]></title><description><![CDATA[Lecture 6 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/lecture-6-deploying-model-serving</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/lecture-6-deploying-model-serving</guid><dc:creator><![CDATA[Başak Tuğçe Eskili]]></dc:creator><pubDate>Sat, 02 Aug 2025 11:00:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/Rt8qtjskuSs" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition,</strong> which opened the door for us to create a free hands-on course on <strong>MLOps with Databricks</strong>.</p><p>This article is part of that course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>This is lecture 6 (out of 10). Let&#8217;s dive into deploying model serving endpoints and implementing A/B testing on Databricks. You can also follow along with the full walkthrough on the Marvelous MLOps YouTube channel.</p><div id="youtube2-Rt8qtjskuSs" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Rt8qtjskuSs&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Rt8qtjskuSs?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>In previous lectures, you learned how to train, log, and register models with MLflow. Now, it&#8217;s time to expose those models behind an API using Databricks Model Serving.</p><p>Databricks Model Serving is a fully managed, serverless solution that allows you to deploy MLflow models as RESTful APIs without the need to set up or manage any infrastructure.</p><ul><li><p>Effortless deployment of registered MLflow models</p></li><li><p>Automatic scaling, including scale-to-zero when there&#8217;s no traffic</p></li><li><p>Built-in monitoring in the Databricks UI (track latency, throughput, error rates)</p></li><li><p>Seamless integration with models registered in Unity Catalog</p></li></ul><h3>Model serving limitations</h3><p>Databricks model serving makes the transition from experimentation to production incredibly smooth. It&#8217;s ideal for teams who want to focus on building great models, not managing infrastructure. However, if you choose to deploy a model serving endpoint on Databricks, you must be aware of its limitations, such as:</p><ul><li><p>No control over runtime environment: Databricks chooses the environment for you, which can be a constraint if you need specific library versions.</p></li><li><p>No control over cluster size. Each replica is limited to 4 GB RAM (CPU), which may not be enough for very large models.</p></li><li><p>Workload size options: You can choose the workload size (Small, Medium, Large, XL, etc.), which determines the number of compute units per replica. For demanding use cases, you can scale up to 512 units per endpoint on request.</p></li></ul><p>The workload size determines the number of compute units available, with each unit able to handle one request at a time (4 for Small, 8&#8211;16 for Medium, 16&#8211;64 for Large). This does <strong>not</strong> directly translate to queries per second (QPS), as throughput depends on the execution time of the model&#8217;s <code>predict</code> function. For example, if prediction takes 20&#8239;ms, 4 compute units can handle approximately <code>4 / 0.02 = 200 QPS</code>.</p><p>Autoscaling is based on the number of required units, not CPU or RAM usage. The number of required units is calculated as: </p><ul><li><p>Required units = Queries per second &#215; Model Processing Time (in seconds).</p></li></ul><p>Example:  1,000 QPS &#215; 0.02s = 20 units needed.</p><h3>Payload structure</h3><p>Another serving limitation is the payload structure. Databricks uses MLflow serving behind the scenes, and the payload is defined by it. You have some ability to adapt the payload for your needs by using pyfunc, but you have no influence on the global payload structure.</p><p>This is not ideal if you already have existing integrations, and want to migrate to Databricks model serving.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7FA0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7FA0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 424w, https://substackcdn.com/image/fetch/$s_!7FA0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 848w, https://substackcdn.com/image/fetch/$s_!7FA0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 1272w, https://substackcdn.com/image/fetch/$s_!7FA0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7FA0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png" width="1456" height="454" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:454,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7FA0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 424w, https://substackcdn.com/image/fetch/$s_!7FA0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 848w, https://substackcdn.com/image/fetch/$s_!7FA0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 1272w, https://substackcdn.com/image/fetch/$s_!7FA0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ef85bb-7277-44d6-9518-4c00041d2d24_1600x499.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Model deployment</h3><p>Let&#8217;s go through notebooks/lecture6.deploy_model_serving_endpoint.py notebook. You can find the code repository <a href="https://github.com/marvelousmlops/marvel-characters">here.</a></p><p>Before we proceed with the model deployment, we need to authenticate with Databricks and set up our environment (the details on authentication are covered in <a href="https://marvelousmlops.substack.com/p/developing-on-databricks">lecture 2</a>). The code supports both running inside Databricks and running locally (e.g., from VS Code). Authentication details are loaded from environment variables or the .env file, ensuring a smooth developer experience for teams.</p><p>We also define the DBR_TOKEN and DBR_HOST environment variables, which will be used later to send requests to the deployed endpoint.</p><pre><code>from databricks.sdk import WorkspaceClient
import os
from dotenv import load_dotenv
import mlflow
from marvel_characters.utils import is_databricks

w = WorkspaceClient()
os.environ["DBR_HOST"] = w.config.host
os.environ["DBR_TOKEN"] = w.tokens.create(lifetime_seconds=1200).token_value

if not is_databricks():
    load_dotenv()
    profile = os.environ["PROFILE"]
    mlflow.set_tracking_uri(f"databricks://{profile}")
    mlflow.set_registry_uri(f"databricks-uc://{profile}")</code></pre><p>The deployment leverages a custom ModelServing utility that wraps Databricks Model Serving APIs. Here&#8217;s how you deploy or update the endpoint:</p><pre><code>from marvel_characters.serving.model_serving import ModelServing

model_serving = ModelServing(
    model_name=f"{catalog_name}.{schema_name}.marvel_character_model_custom",
    endpoint_name="marvel-character-model-serving"
)

model_serving.deploy_or_update_serving_endpoint()</code></pre><p>Let&#8217;s take a look how the ModelServing class and the deploy_or_update_serving_endpoint() method look like. Essentially, it is a wrapper around Databricks sdk, where we take the latest registered model by the latest-model alias, and use it to create or update the endpoint if it already exists.</p><pre><code><code>import mlflow
from databricks.sdk import WorkspaceClient
from databricks.sdk.service.serving import (
    EndpointCoreConfigInput,
    ServedEntityInput,
)


class ModelServing:
    """Manages model serving in Databricks for Marvel characters."""

    def __init__(self, model_name: str, endpoint_name: str) -&gt; None:
        """Initialize the Model Serving Manager.
        """
        self.workspace = WorkspaceClient()
        self.endpoint_name = endpoint_name
        self.model_name = model_name

    def get_latest_model_version(self) -&gt; str:
        """Retrieve the latest version of the model.
        """
        client = mlflow.MlflowClient()
        latest_version = client.get_model_version_by_alias(self.model_name, alias="latest-model").version
        print(f"Latest model version: {latest_version}")
        return latest_version
    def deploy_or_update_serving_endpoint(
        self, version: str = "latest", workload_size: str = "Small", scale_to_zero: bool = True
    ) -&gt; None:
        """Deploy or update the model serving endpoint in Databricks for Marvel characters.
        """
        endpoint_exists = any(item.name == self.endpoint_name for item in self.workspace.serving_endpoints.list())
        entity_version = self.get_latest_model_version() if version == "latest" else version

        served_entities = [
            ServedEntityInput(
                entity_name=self.model_name,
                scale_to_zero_enabled=scale_to_zero,
                workload_size=workload_size,
                entity_version=entity_version,
            )
        ]

        if not endpoint_exists:
            self.workspace.serving_endpoints.create(
                name=self.endpoint_name,
                config=EndpointCoreConfigInput(
                    served_entities=served_entities,
                ),
            )
        else:
      self.workspace.serving_endpoints.update_config(name=self.endpoint_name, served_entities=served_entities)
</code></code></pre><p>After the endpoint is deployed, we can view it in the workspace UI:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vHpx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vHpx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 424w, https://substackcdn.com/image/fetch/$s_!vHpx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 848w, https://substackcdn.com/image/fetch/$s_!vHpx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 1272w, https://substackcdn.com/image/fetch/$s_!vHpx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vHpx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:190371,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://marvelousmlops.substack.com/i/169913939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!vHpx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 424w, https://substackcdn.com/image/fetch/$s_!vHpx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 848w, https://substackcdn.com/image/fetch/$s_!vHpx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 1272w, https://substackcdn.com/image/fetch/$s_!vHpx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc638a73-af98-48bd-9d11-6ab0b6ba4ab8_1926x1076.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Making Predictions</h3><p>After deployment, you can send prediction requests to your endpoint. The code demonstrates how to sample records from your test set, format them as JSON, and call the endpoint using Python&#8217;s requests library:</p><pre><code>import requests

def call_endpoint(record):
    """
    Calls the model serving endpoint with a given input record.
    """
    serving_endpoint = f"{os.environ['DBR_HOST']}/serving-endpoints/marvel-character-model-serving/invocations"
    
    print(f"Calling endpoint: {serving_endpoint}")
    
    response = requests.post(
        serving_endpoint,
        headers={"Authorization": f"Bearer {os.environ['DBR_TOKEN']}"},
        json={"dataframe_records": record},
    )
    return response.status_code, response.text

sampled_records = test_set[required_columns].sample(n=18000, replace=True)
sampled_records = sampled_records.replace({np.nan: None}).to_dict(orient="records")

dataframe_records = [[record] for record in sampled_records]

status_code, response_text = call_endpoint(dataframe_records[0])</code></pre><p>This enables you to validate your endpoint with real data and integrate it into downstream applications.</p><h3>A/B Testing with Databricks Model Serving</h3><p>Serving a single model is great, but how do you compare two models in production? A/B testing is a critical technique for continuous improvement and experimentation.</p><p>Databricks supports traffic split, by defining the list of served entities and percentage of traffic that goes to these entities. A common misconception is that simply splitting traffic between model versions qualifies as A/B testing but that&#8217;s not accurate. True A/B testing requires that a customer consistently sees the same model version throughout the experiment. This consistency is critical for accurately measuring performance differences (click-through rate, conversion, etc.).</p><p>Naive traffic split (like Databricks&#8217; built-in routing) doesn&#8217;t guarantee this. A user might hit model A on one request and model B on the next. Sticky assignment is needed: Assign each user to a model variant based on a stable identifier (e.g., user ID or session ID), so they always get the same model during the test.</p><p>Let&#8217;s demonstrate how this can be implemented.</p><p>In the notebook notebooks/lecture6.ab_testing.py, we train two different models (A and B), each with its own configuration or hyperparameters:</p><pre><code>from marvel_characters.models.basic_model import BasicModel

basic_model_a = BasicModel(config=config, tags=tags, spark=spark)
basic_model_a.train()

basic_model_a.log_model()

basic_model_a.register_model()

model_A_uri = f"models:/{basic_model_a.model_name}@latest-model"

basic_model_b = BasicModel(config=config, tags=tags, spark=spark)

basic_model_b.parameters = {"learning_rate": 0.01, "n_estimators": 1000, "max_depth": 6}

basic_model_b.model_name = f"{catalog_name}.{schema_name}.marvel_character_model_basic_B"

basic_model_b.train()

basic_model_b.log_model()

basic_model_b.register_model()

model_B_uri = f"models:/{basic_model_b.model_name}@latest-model"</code></pre><p>Then, we define a custom MLflow PyFunc model (MarvelModelWrapper) to route requests to Model A or Model B based on a hash of a unique identifier (e.g., Id). This ensures a consistent split for reproducibility:</p><pre><code>import hashlib

class MarvelModelWrapper(mlflow.pyfunc.PythonModel):

   def load_context(self, context):
      self.model_a = mlflow.sklearn.load_model(context.artifacts["sklearn-pipeline-model-A"])
      self.model_b = mlflow.sklearn.load_model(context.artifacts["sklearn-pipeline-model-B"])
      def predict(self, context, model_input):
      page_id = str(model_input["Id"].values[0])
      hashed_id = hashlib.md5(page_id.encode(encoding="UTF-8")).hexdigest()

      if int(hashed_id, 16) % 2:

          predictions = self.model_a.predict(model_input.drop(["Id"], axis=1))
          return {"Prediction": predictions[0], "model": "Model A"}

      else:
          predictions = self.model_b.predict(model_input.drop(["Id"], axis=1))
          return {"Prediction": predictions[0], "model": "Model B"}
</code></pre><p>The context must be passed at the model logging step (just as we did in <a href="https://marvelousmlops.substack.com/p/logging-and-registering-models-with">lecture 4</a>). Once the wrapper is registered and deployed in the same way we showed earlier, prediction requests are automatically split between the two models. You can monitor which model served each prediction and analyze results in the Databricks UI or programmatically.</p><h2>Key Takeaways</h2><p>In this lecture, we deployed our registered MLflow model to a Databricks Model Serving endpoint and exposed it as a REST API. We implemented a custom PyFunc wrapper to control the response payload and demonstrated how it can also be used for A/B testing by routing live traffic between different model versions. </p><p>Finally, we validated the endpoint in real time, showing how this approach supports experimentation while keeping deployment fast and reproducible.</p><p>With this setup, our model is not only live but also ready for iterative improvements in production.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Model serving architectures]]></title><description><![CDATA[Lecture 5 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/model-serving-architectures</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/model-serving-architectures</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Fri, 01 Aug 2025 17:39:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/Qwx1vbvYbDY" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on <strong>MLOps with Databricks</strong>.</p><p>This article is part of the course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>This is lecture 5, where we talk about model serving architectures on Databricks. View it on Marvelous MLOps YouTube channel:</p><div id="youtube2-Qwx1vbvYbDY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Qwx1vbvYbDY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Qwx1vbvYbDY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>Model serving is a challenging topic for many machine learning teams. In an ideal scenario, the same team that develops a model, should be responsible for model deployment. However, this is not always feasible due to the knowledge gap or organizational structure. In that scenario, once model is ready, it is handed over to another team for deployment. It creates a lot of overhead when it comes to debugging and communication.</p><p>That&#8217;s where Databricks model serving can help a lot. Databricks model and feature serving use serverless, which simplifies the infrastructure side of the deployment, and a model endpoint can be created with one Python command (using Databricks sdk). It allows data science teams to own the deployment end-to-end and minimize the dependence on other teams.</p><p>In this article, we&#8217;ll discuss the following architectures:</p><ul><li><p>serving batch predictions (feature serving)</p></li><li><p>model serving</p></li><li><p>model serving with feature lookup</p></li></ul><h4>Feature serving</h4><p>Serving batch predictions is probably one of the most popular and underestimated types of machine learning model deployment. Predictions are computed in advance using a batch process, stored in an SQL or in-memory database, and retrieved at request.</p><p>This architecture is very popular in the case of personal recommendation with low latency requirements. For example, an e-commerce store may recommend products to customers on various pages of the website.</p><p>Databricks Feature Serving is a perfect fit here. A scheduled <strong>Lakeflow</strong> job preprocesses data, retrains the model, and writes predictions to a feature table in <strong>Unity Catalog</strong>. These features are synced to an <strong>Online Store</strong> and exposed through a <strong>Feature Serving endpoint</strong>, defined by a <strong>FeatureSpec, </strong>a blueprint that combines feature functions (how features are calculated) with feature lookups (how they&#8217;re retrieved). Your application can then query this endpoint in real time to get fresh features for inference.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BjnZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BjnZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 424w, https://substackcdn.com/image/fetch/$s_!BjnZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 848w, https://substackcdn.com/image/fetch/$s_!BjnZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 1272w, https://substackcdn.com/image/fetch/$s_!BjnZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BjnZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png" width="940" height="486" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:486,&quot;width&quot;:940,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BjnZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 424w, https://substackcdn.com/image/fetch/$s_!BjnZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 848w, https://substackcdn.com/image/fetch/$s_!BjnZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 1272w, https://substackcdn.com/image/fetch/$s_!BjnZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f6fb8da-8113-41e5-9f49-ddb0bfa59167_940x486.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Model serving</h4><p>Model serving assumes that model is deployed behind and endpoint, and all features are available through the payload. This is not the most realistic scenario, but can be still used in certain use cases, when models are embedded into apps and rely on user input from the app.</p><p>The figure below illustrates an automated model retraining and deployment pipeline on Databricks. A scheduled Lakeflow job preprocesses the data and retrains the model, which is then registered in Unity Catalog. The latest model is used to update a real-time serving endpoint, ensuring that applications always have access to the most up-to-date model.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ocEQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ocEQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 424w, https://substackcdn.com/image/fetch/$s_!ocEQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 848w, https://substackcdn.com/image/fetch/$s_!ocEQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 1272w, https://substackcdn.com/image/fetch/$s_!ocEQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ocEQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png" width="698" height="354" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:354,&quot;width&quot;:698,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ocEQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 424w, https://substackcdn.com/image/fetch/$s_!ocEQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 848w, https://substackcdn.com/image/fetch/$s_!ocEQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 1272w, https://substackcdn.com/image/fetch/$s_!ocEQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85ea38a-885b-4e26-9205-d2f24aa6e07c_698x354.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is the use case we cover in this course, and this is what we will deploy in the follow-up lecture, as Databricks Online store is not supported in Databricks Free edition.</p><h4>Model serving with feature lookup</h4><p>Model serving with feature lookup is the most complex (and most realistic) scenario that combines the ones we went through already. This architecture can be used for fraud detection, more complex recommendations, and many other use cases.</p><p>Here, some input features are passed with a request, some are retrieved from an Online Store (those features can be computed in a batch or streaming fashion, depending on your needs).</p><p>The figure below shows a simplified architecture for model serving with feature lookup. A scheduled Lakeflow job preprocesses data and updates a feature table in Unity Catalog, which is then synced to the online store. Another scheduled job retrains the model using the latest features and registers it in Unity Catalog. During inference, the model serving endpoint fetches features from the online store using a primary key, combines them with any features provided in the request payload, runs the model&#8217;s prediction function, and returns the result to the application.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L-D2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L-D2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 424w, https://substackcdn.com/image/fetch/$s_!L-D2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 848w, https://substackcdn.com/image/fetch/$s_!L-D2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 1272w, https://substackcdn.com/image/fetch/$s_!L-D2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L-D2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png" width="936" height="456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:456,&quot;width&quot;:936,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!L-D2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 424w, https://substackcdn.com/image/fetch/$s_!L-D2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 848w, https://substackcdn.com/image/fetch/$s_!L-D2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 1272w, https://substackcdn.com/image/fetch/$s_!L-D2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff43c2d1d-7aed-4782-9609-e2d8d900e149_936x456.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Conclusion</h4><p>Databricks now supports a wide range of machine learning model deployment architectures. Even though they come with some limitations (for example, you have limited influence on the payload structure), it significantly simplifies the deployment.</p><p>In the next lecture, we&#8217;ll dive into implementation of model serving endpoint and show how the registered model can be used to expose real&#8209;time predictions for downstream applications.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Logging and registering models with MLflow]]></title><description><![CDATA[Lecture 4 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/logging-and-registering-models-with</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/logging-and-registering-models-with</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Thu, 31 Jul 2025 18:49:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/ui0gRZEcEpM" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on <strong>MLOps with Databricks</strong>.</p><p>This article is part of the course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>Let&#8217;s dive into lecture 4 where we talk about logging and registering models with MLflow. View the lecture on Marvelous MLOps YouTube channel:</p><div id="youtube2-ui0gRZEcEpM" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ui0gRZEcEpM&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ui0gRZEcEpM?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>In the <a href="https://marvelousmlops.substack.com/p/getting-started-with-mlflow">previous lecture</a>, we have logged metrics, parameters, various artifacts, but have not logged a model yet. You could just saved a model in a .pkl file, but MLflow goes beyond that: it provides a standardized format called an MLflow Model, which defines how a model, its dependencies, and its code are stored. This is essential for downstream tasks like real-time serving, which will be covered later in the course.</p><p>A model can be logged using the mlflow.&lt;model_flavor&gt;.log_model() function. MLflow supports a wide range of flavors, such as lightgbm, prophet, pytorch, sklearn, xgboost, and many more. It also supports any custom model logics through PythonModel base class , which can be logged using pyfunc flavor.</p><h3>Basic model: log, train, and register</h3><p>To demonstrate logging, we&#8217;ll start with training a scikit-learn pipeline (referred to as Basic model) and logging it using sklearn flavor. We&#8217;ll walk through the notebooks/lecture4.train_register_basic_model.py code from the <a href="https://github.com/marvelousmlops/marvel-charactershttps://github.com/marvelousmlops/marvel-characters">course GitHub repo</a>.</p><p>Since we are interacting with MLflow, we need to set up tracking and registry URIs just as we did in <a href="https://marvelousmlops.substack.com/p/getting-started-with-mlflow">lecture 3</a>:</p><pre><code>import mlflow
import os
from dotenv import load_dotenv

def is_databricks():
    return "DATABRICKS_RUNTIME_VERSION" in os.environ

if not is_databricks():
    load_dotenv()
    profile = os.environ["PROFILE"]
    mlflow.set_tracking_uri(f"databricks://{profile}")
    mlflow.set_registry_uri(f"databricks-uc://{profile}")</code></pre><p>Then we&#8217;ll load the project configuration, initialize the SparkSession, and define tags we&#8217;ll need to tag the MLflow run and registered model:</p><pre><code>from pyspark.sql import SparkSession

from marvel_characters.config import ProjectConfig, Tags

config = ProjectConfig.from_yaml(config_path="../project_config_marvel.yml", env="dev")
spark = SparkSession.builder.getOrCreate()
tags = Tags(**{"git_sha": "abcd12345", "branch": "main"})</code></pre><p>We&#8217;ll need those to initialize an instance of BasicModel class. Then we load the data, prepare features, train and log the model:</p><pre><code>from marvel_characters.models.basic_model import BasicModel

basic_model = BasicModel(config=config,
                         tags=tags,
                         spark=spark)

basic_model.load_data()
basic_model.prepare_features()

basic_model.train()

basic_model.log_model()</code></pre><p>Let&#8217;s go through the logics behind the BasicModel class to understand what&#8217;s going on. After the class gets initialized, we set certain class attributes such as features, target, parameters, and model name.</p><p>We load the train and the test set using pyspark, and we&#8217;ll need these pyspark dataframes later to log the model input, together with the delta table version we retrieve. We also use toPandas() command to create pandas dataframes which are used for model training and evaluation.</p><p>Note that toPandas() command is rather inefficient, and if your dataset is large, you may want to look for alternatives, such as using deltatable package and external credentials vending in the way described in an <a href="https://marvelousmlops.substack.com/p/using-polars-in-unison-with-databricks">earlier article</a>. Logging input data in this case can be quite challenging.</p><pre><code>import mlflow
import pandas as pd
from delta.tables import DeltaTable
from lightgbm import LGBMClassifier
from loguru import logger
from mlflow import MlflowClient
from mlflow.models import infer_signature
from pyspark.sql import SparkSession
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

from marvel_characters.config import ProjectConfig, Tags

class BasicModel:
    """A basic model class for Marvel character survival prediction 
       using LightGBM.
    """

    def __init__(self, config: ProjectConfig, 
                 tags: Tags, spark: SparkSession) -&gt; None:

        self.config = config
        self.spark = spark
        self.tags = tags.to_dict()

        # Extract settings from the config
        self.num_features = self.config.num_features
        self.cat_features = self.config.cat_features
        self.target = self.config.target
        self.parameters = self.config.parameters
        self.catalog_name = self.config.catalog_name
        self.schema_name = self.config.schema_name
        self.experiment_name = self.config.experiment_name_basic
        self.model_name = f"{self.catalog_name}.{self.schema_name}.marvel_character_model_basic"
    
def load_data(self) -&gt; None:
        """Load training and testing data from Delta tables.
        """
        logger.info("&#128260; Loading data from Databricks tables...")
        self.train_set_spark = self.spark.table(f"{self.catalog_name}.{self.schema_name}.train_set")
        self.train_set = self.train_set_spark.toPandas()
        self.test_set_spark = self.spark.table(f"{self.catalog_name}.{self.schema_name}.test_set")
        self.test_set =  self.test_set_spark.toPandas()

        self.X_train = self.train_set[self.num_features + self.cat_features]
        self.y_train = self.train_set[self.target]
        self.X_test = self.test_set[self.num_features + self.cat_features]
        self.y_test = self.test_set[self.target]
        self.eval_data = self.test_set[self.num_features + self.cat_features + [self.target]]

        train_delta_table = DeltaTable.forName(self.spark,
                                               f"{self.catalog_name}.{self.schema_name}.train_set")
        self.train_data_version = str(train_delta_table.history().select("version").first()[0])
        test_delta_table = DeltaTable.forName(self.spark,
                                               f"{self.catalog_name}.{self.schema_name}.test_set")
        self.test_data_version = str(test_delta_table.history().select("version").first()[0])
        logger.info("&#9989; Data successfully loaded.")</code></pre><p>The next method defined in the class is prepare_features(), which defines the sklearn pipeline that consists of 2 steps: encoding categorical variables using a custom encoder CatToIntTransofrmer, and LGBMClassifier.</p><p>LightGBM supports integer-encoded categorical features, which generally <a href="https://lightgbm.readthedocs.io/en/latest/Advanced-Topics.html">performs better than one-hot encoding</a>. A custom encoder is necessary to make sure the LightGBM model treats integer-encoded features as categorical features, and earlier unseen categories get value -1 assigned to avoid errors while computing predictions.</p><p>You may notice that the CatToIntTransformer class is defined inside the prepare_features method. While this isn&#8217;t ideal from a design standpoint, it keeps the model self-contained, and we do not need to log our private package together with the model if we want to use the model for the downstream tasks. We&#8217;ll show a better to handle private dependencies when we discuss a custom pyfunc model later in this article.</p><pre><code>def prepare_features(self) -&gt; None:
        """Encode categorical features and define a preprocessing pipeline.
        """
        logger.info("&#128260; Defining preprocessing pipeline...")

        class CatToIntTransformer(BaseEstimator, TransformerMixin):
            """Transformer that encodes categorical columns as 
               integer codes for LightGBM.

            Unknown categories at transform time are encoded as -1.
            """

            def __init__(self, cat_features: list[str]) -&gt; None:
                """Initialize the transformer with categorical feature names."""
                self.cat_features = cat_features
                self.cat_maps_ = {}

            def fit(self, X: pd.DataFrame, y=None) -&gt; None:
                """Fit the transformer to the DataFrame X."""
                self.fit_transform(X)
                return self

            def fit_transform(self, X: pd.DataFrame, y=None) -&gt; pd.DataFrame:
                """Fit and transform the DataFrame X."""
                X = X.copy()
                for col in self.cat_features:
                    c = pd.Categorical(X[col])
                    # Build mapping: {category: code}
                    self.cat_maps_[col] = dict(zip(c.categories, 
                                      range(len(c.categories)), strict=False))
                    X[col] = X[col].map(lambda val, col=col: self.cat_maps_[col].get(val, -1)).astype("category")
                return X

            def transform(self, X: pd.DataFrame) -&gt; pd.DataFrame:
                """Transform the DataFrame X by encoding categorical features as integers."""
                X = X.copy()
                for col in self.cat_features:
                    X[col] = X[col].map(lambda val, col=col: self.cat_maps_[col].get(val, -1)).astype("category")
                return X

        preprocessor = ColumnTransformer(
            transformers=[("cat", CatToIntTransformer(self.cat_features), self.cat_features)],
            remainder="passthrough"
        )
        self.pipeline = Pipeline(
            steps=[("preprocessor", preprocessor),
                   ("classifier", LGBMClassifier(**self.parameters))])
        logger.info("&#9989; Preprocessing pipeline defined.")</code></pre><p>The train() fits the pipeline, and the log_model() method logs the model with all the required information:</p><ul><li><p><strong>Signature</strong> is inferred using model input (X_train) and model output (the result of running the predict function on the pipeline), and passed when logging the model. If the signature is not provided, we would not be able to register model in Unity Catalog later.</p></li><li><p><strong>Input datasets</strong> (train and test sets, including the delta table version) are logged under the MLflow run to ensure that we can get the exact version of data used for training and evaluation, even if data was modified later, thanks to the time travel functionality of delta tables. Remember to set a proper retention period on the delta table (default is 7 days), otherwise you may not be able to access the exact version of the table if VACUUM command was executed. Most accounts have predictive optimization enabled by default, which means that Databricks automatically executes it as part of the optimization process.</p></li></ul><pre><code> def train(self) -&gt; None:
        """Train the model."""
        logger.info("&#128640; Starting training...")
        self.pipeline.fit(self.X_train, self.y_train)

    def log_model(self) -&gt; None:
        """Log the model using MLflow."""
        mlflow.set_experiment(self.experiment_name)
        with mlflow.start_run(tags=self.tags) as run:
            self.run_id = run.info.run_id

            signature = infer_signature(model_input=self.X_train,
                                        model_output=self.pipeline.predict(self.X_train))
            train_dataset = mlflow.data.from_spark(
                self.train_set_spark,
                table_name=f"{self.catalog_name}.{self.schema_name}.train_set",
                version=self.train_data_version,
            )
            mlflow.log_input(train_dataset, context="training")
            test_dataset = mlflow.data.from_spark(
                self.test_set_spark,
                table_name=f"{self.catalog_name}.{self.schema_name}.test_set",
                version=self.test_data_version,
            )
            mlflow.log_input(test_dataset, context="testing")
            self.model_info = mlflow.sklearn.log_model(
                sk_model=self.pipeline,
                artifact_path="lightgbm-pipeline-model",
                signature=signature,
                input_example=self.X_test[0:1]
            )

            result = mlflow.models.evaluate(
                    self.model_info.model_uri,
                    self.eval_data,
                    targets=self.config.target,
                    model_type="classifier",
                    evaluators=["default"],
                )
            self.metrics = result.metrics</code></pre><p>Notice that we do not log any metrics. The metrics gets computed and logged under the same run using the mlflow.models.evaluate() function, which requires model URI, evaluation data, target, model type, and evaluators to run. Here, we use default evaluators, which means that standard metrics from the default evaluator gets logged:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2pXX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2pXX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 424w, https://substackcdn.com/image/fetch/$s_!2pXX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 848w, https://substackcdn.com/image/fetch/$s_!2pXX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 1272w, https://substackcdn.com/image/fetch/$s_!2pXX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2pXX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png" width="978" height="1090" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1090,&quot;width&quot;:978,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2pXX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 424w, https://substackcdn.com/image/fetch/$s_!2pXX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 848w, https://substackcdn.com/image/fetch/$s_!2pXX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 1272w, https://substackcdn.com/image/fetch/$s_!2pXX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dda1a7-f359-433b-b3db-0ca2e26e2d05_978x1090.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After the model is logged, we can get the logged model using the model id (we can also use model id in the model URI to load the model):</p><pre><code>logged_model = mlflow.get_logged_model(basic_model.model_info.model_id)
model = mlflow.sklearn.load_model(f"models:/{basic_model.model_info.model_id}")</code></pre><p>This was not possible before MLflow 3, which introduced the concept of the <strong>LoggedModel. </strong>We also now have a separate model tab under the MLflow experiments in the UI. Let&#8217;s inspect the LoggedModel class. On purpose, I removed some metrics from the illustration (in fact, there is a separate entry for each metric shown in the table from the UI earlier).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Z6QQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 424w, https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 848w, https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 1272w, https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png" width="1210" height="1378" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1378,&quot;width&quot;:1210,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 424w, https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 848w, https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 1272w, https://substackcdn.com/image/fetch/$s_!Z6QQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92f4f441-6004-4485-ab61-66167cd7b55d_1210x1378.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It&#8217;s possible to access the model&#8217;s metrics and parameters (we have not logged any) directly from the <strong>LoggedModel</strong> class (which was only possible via the MLflow run in earlier versions of MLflow):</p><pre><code>logged_model.params
logged_model.metrics</code></pre><p>We still nee the run object to retrieve the information about the dataset inputs which were used to train and to evaluate the model:</p><pre><code>run = mlflow.get_run(basic_model.run_id)


inputs = run.inputs.dataset_inputs
training_input = next((x for x in inputs if len(x.tags)&gt;0 and x.tags[0].value == 'training'), None)
training_source = mlflow.data.get_source(training_input)
training_source.load()

testing_input = next((x for x in inputs if len(x.tags)&gt;0 and x.tags[0].value == 'testing'), None)
testing_source = mlflow.data.get_source(testing_input)
testing_source.load()</code></pre><p>The BasicModel class has another method, register_model(), which registers model in the Unity Catalog, together with the provided tags.</p><pre><code>def register_model(self) -&gt; None:
        """Register model in Unity Catalog."""
        logger.info("&#128260; Registering the model in UC...")
        registered_model = mlflow.register_model(
            model_uri=self.model_info.model_uri,
            name=self.model_name,
            tags=self.tags,
        )
        logger.info(f"&#9989; Model registered as version {registered_model.version}.")

        latest_version = registered_model.version

        client = MlflowClient()
        client.set_registered_model_alias(
            name=self.model_name,
            alias="latest-model",
            version=latest_version,
        )
        return latest_version</code></pre><p>Notice that we set the &#8220;latest-model&#8221; alias to make it easy to find the latest version of the registered model. &#8220;Latest&#8221; is a reserved value for the alias and can&#8217;t be used, and models can&#8217;t be referred as &#8220;latest&#8221; either.</p><p>Searching for model versions is pretty hard otherwise: you can only search by model name or alias. Searching using filter strings is not supported when model is registered in Unity Catalog.</p><h3>Wrapping the model using pyfunc</h3><p>Model signature in MLflow defines how different interfaces interact with the model. For instance, it defines the payload of the endpoint if the model gets served using Databricks model serving.</p><p>We&#8217;ve just registered a sklearn pipeline. If we deploy it behind an endpoint and query it, we will get an output in the format: {&#8220;Predictions&#8221;: [0]}. A pyfunc model flavor becomes useful if we want to adjust the model payload.</p><p>There are other scenarios when you may want to use a pyfunc. For example, if we need to access other systems (for example, a database) to return predictions, or if model serving requires specific artifacts (other files or even models).</p><p>Essentially, we are using pyfunc as a wrapper (In a certain sense, it&#8217;s very similar to the functionality of a FastAPI). Keeping the definition of the payload separate from the model itself is convenient : we can easily adjust the pyfunc wrapper definition without touching the registered model itself.</p><p>Let&#8217;s demonstrate how a pyfunc wrapper can be used. Under the custom_model module of the marvel-characters package, we defined the MarvelModelWrapper class. It has the load_context method which loads the basic model we trained earlier. The basic model gets loaded from the context, which gets stored together with the logged pyfunc model when we run the mlflow.pyfunc.log_model() function.</p><p>Notice that the predict method uses the adjust_predictions function defined outside of the MarvelModelWrapper, which means that the marvel_characters package must be now logged together with the pyfunc wrapper.</p><pre><code>from datetime import datetime

import mlflow
import numpy as np
import pandas as pd
from mlflow import MlflowClient
from mlflow.models import infer_signature
from mlflow.pyfunc import PythonModelContext
from mlflow.utils.environment import _mlflow_conda_env

from marvel_characters.config import Tags


def adjust_predictions(predictions):
    return {"Survival prediction": ["alive" if pred == 1 else "dead" for pred in predictions]}

class MarvelModelWrapper(mlflow.pyfunc.PythonModel):

    def load_context(self, context: PythonModelContext) -&gt; None:
        self.model = mlflow.sklearn.load_model(
            context.artifacts["lightgbm-pipeline"]
        )

    def predict(self, context: PythonModelContext, model_input: pd.DataFrame | np.ndarray) -&gt; dict:
        predictions = self.model.predict(model_input)
        return adjust_predictions(predictions)</code></pre><p>Let&#8217;s take a look at the log_register_model method. It takes the code_paths argument, which contains a local path to the marvel_characters package wheel. We use this list to define the conda_env. The location of the wheel in the artifacts folder (it will be saved in the code folder) is defined as a dependency.</p><p>Both code_paths and conda_env must be passed as an argument to the mlflow.pyfunc.log_model() function. Here, we also pass artifacts, which is a dictionary that contains the basic model URI.</p><pre><code>def log_register_model(self, wrapped_model_uri: str, pyfunc_model_name: str,
                           experiment_name: str, tags: Tags, code_paths: list[str],
                           input_example: pd.DataFrame) -&gt; None:
        mlflow.set_experiment(experiment_name=experiment_name)
        with mlflow.start_run(run_name=f"wrapper-lightgbm-{datetime.now().strftime('%Y-%m-%d')}",
            tags=tags.to_dict()):
            additional_pip_deps = []
            for package in code_paths:
                whl_name = package.split("/")[-1]
                additional_pip_deps.append(f"code/{whl_name}")
            conda_env = _mlflow_conda_env(additional_pip_deps=additional_pip_deps)

            signature = infer_signature(model_input=input_example,
                                        model_output={"Survival prediction": ["alive"]})
            model_info = mlflow.pyfunc.log_model(
                python_model=self,
                name="pyfunc-wrapper",
                artifacts={
                    "lightgbm-pipeline": wrapped_model_uri},
                signature=signature,
                code_paths=code_paths,
                conda_env=conda_env,
            )
        client = MlflowClient()
        registered_model = mlflow.register_model(
                model_uri=model_info.model_uri,
                name=pyfunc_model_name,
                tags=tags.to_dict(),
            )
        latest_version = registered_model.version
        client.set_registered_model_alias(
            name=pyfunc_model_name,
            alias="latest-model",
            version=latest_version,
        )
        return latest_version</code></pre><p>The pyfunc wrapper gets registered in the same way as the basic model, here we also set the &#8220;latest-model&#8221; alias. This is how we log and register the pyfunc wrapper in notebooks/lecture4.train_register_custom_model.py:</p><pre><code>from importlib.metadata import version

marvel_characters_v = version("marvel_characters")

code_paths=[f"../dist/marvel_characters-{marvel_characters_v}-py3-none-any.whl"]

client = MlflowClient()
wrapped_model_version = client.get_model_version_by_alias(
    name=f"{config.catalog_name}.{config.schema_name}.marvel_character_model_basic",
    alias="latest-model")

test_set = spark.table(f"{config.catalog_name}.{config.schema_name}.test_set").toPandas()
X_test = test_set[config.num_features + config.cat_features]

pyfunc_model_name = f"{config.catalog_name}.{config.schema_name}.marvel_character_model_custom"
wrapper = MarvelModelWrapper()
wrapper.log_register_model(wrapped_model_uri=f"models:/{wrapped_model_version.model_id}",
                           pyfunc_model_name=pyfunc_model_name,
                           experiment_name=config.experiment_name_custom,
                           input_example=X_test[0:1],
                           tags=tags,
                           code_paths=code_paths)</code></pre><p>After the pyfunc model is logged and registered, we can see in the UI how its artifacts are stored. We can find the basic model&#8217;s artifacts in the artifacts folder, and the package wheel in the code folder. Notice that the wheel is referenced in the requirements.txt. When the environment gets created, all the dependencies of our private package get installed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-T00!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-T00!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 424w, https://substackcdn.com/image/fetch/$s_!-T00!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 848w, https://substackcdn.com/image/fetch/$s_!-T00!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 1272w, https://substackcdn.com/image/fetch/$s_!-T00!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-T00!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png" width="1456" height="705" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:705,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-T00!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 424w, https://substackcdn.com/image/fetch/$s_!-T00!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 848w, https://substackcdn.com/image/fetch/$s_!-T00!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 1272w, https://substackcdn.com/image/fetch/$s_!-T00!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf006b23-0cc6-4171-b6e0-ecedf63e9313_1600x775.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The model can be loaded using the mlflow.pyfunc.load_model() function. If we want to access the original MarvelModelWrapper class and its attributes, we must use the unwrap_python_model() method.</p><p>We can run the predict function after we loaded the model. However, this does not guarantee that the model will be loaded successfully at the serving step. That&#8217;s because we are utilizing our existing environment.</p><pre><code>loaded_pufunc_model = mlflow.pyfunc.load_model(f"models:/{pyfunc_model_name}@latest-model")

unwraped_model = loaded_pufunc_model.unwrap_python_model()

unwraped_model.predict(context=None, model_input=X_test[0:1])</code></pre><p>There is a more reliable way that mimics the creation of model serving environment. Note that this code only runs from Databricks environment and would not work in the VS Code.</p><pre><code>predictions = mlflow.models.predict(
    f"models:/{pyfunc_model_name}@latest-model",
    X_test[0:1])</code></pre><h3>Conclusions</h3><p>In this lecture, we went beyond logging metrics and parameters, and logged and registered a model with MLflow. We made sure to capture the model signature, dataset versions, and tags containing the code version (for now, just a dummy value) so our runs are fully reproducible.</p><p>We registered the model in Unity Catalog and wrapped it in a pyfunc to control the output and package extra dependencies for serving.</p><p>Next up, we&#8217;ll dive into model serving architectures and see how all of this comes together in production.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Getting started with MLflow]]></title><description><![CDATA[Lecture 3 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/getting-started-with-mlflow</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/getting-started-with-mlflow</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Wed, 30 Jul 2025 13:47:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/ze5zjnu3eGE" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on <strong>MLOps with Databricks</strong>.</p><p>This article is part of the course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>Let&#8217;s dive into lecture 3, where we talk about MLflow experiment tracking. View the lecture on Marvelous MLOps YouTube channel:</p><div id="youtube2-ze5zjnu3eGE" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ze5zjnu3eGE&quot;,&quot;startTime&quot;:&quot;4s&quot;,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ze5zjnu3eGE?start=4s&amp;rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><strong>MLflow</strong> is probably the most popular tool for <strong>model registry</strong> and <strong>experiment tracking</strong> out there. MLFlow is open source and integrates with a lot of platforms and tools.</p><p>Due to its extensive support and a lot of options, getting started with MLflow may feel overwhelming. In this lecture, we will get back to the basics, and will review 2 most important classes in MLFlow that form the foundation of everything else<strong>, mlflow.entities.Experiment and mlflow.entities.Run.</strong></p><p>We will see how those entities get created, how you can retrieve them, and how they change based on different input parameters. In this course, the Databricks version of MLflow is used, so it contains some Databricks-specific information. However, the idea is generalizable to any MLflow instance.</p><p>Before we go any further, let&#8217;s discuss how we can authenticate towards MLflow tracking server on Databricks.</p><h1><strong>Tracking URI</strong></h1><p>By default, MLflow will track experiment runs using the local file systems, and all the metadata will be stored in the ./mlruns directory. We can verify that by retrieving the current tracking URI:</p><pre><code>import mlflow
mlflow.get_tracking_uri()</code></pre><p>In <a href="https://marvelousmlops.substack.com/p/developing-on-databricks">lecture 2</a>, we explained how we can authenticate towards Databricks using Databricks CLI, which we continued to use when developing in VS Code. Now we must make MLflow aware of it, and use Databricks MLflow tracking server.</p><p>This can be done by calling <code>mlflow.set_tracking_uri().</code>Even though we&#8217;re only using experiment tracking for now, starting with MLflow 3, it&#8217;s also necessary to set the registry URI using <code>mlflow.set_registry_uri()</code>. There are a couple of things to pay attention to:</p><ul><li><p>The tracking and the registry URI must contain the profile that you used to log in (which is defined in the .databrickscfg file, for example<code>dbc-1234a567-b8c9</code>).</p></li><li><p>We want multiple developers with a different profile name to collaborate on the same code base.</p></li><li><p>We only want to set the tracking and the registry URI when running code outside of Databricks.</p></li></ul><p>This leads us to the following possible solution. We can then define whether the code runs within a Databricks environment by checking that the DATABRICKS_RUNTIME_VERSION environment variable is available. Every developer can store the profile name in .env file (which is ignored by git via .gitignore file), and set environment variable PROFILE using Python package dotenv:</p><pre><code>import os
import mlflow
from dotenv import load_dotenv

def is_databricks() -&gt; bool:
    """Check if the code is running in a Databricks environment."""
    return "DATABRICKS_RUNTIME_VERSION" in os.environ

if not is_databricks():
    load_dotenv()
    profile = os.environ.get("PROFILE")
    mlflow.set_tracking_uri(f"databricks://{profile}")
    mlflow.set_registry_uri(f"databricks-uc://{profile}")</code></pre><p>This is how the content of the .env file would look like:</p><pre><code># content of the .env file
PROFILE=dbc-1234a567-b8c9</code></pre><p>Now we use Databricks MLflow as tracking server, we can create an experiment.</p><h1><strong>MLflow Experiment</strong></h1><p>Experiments in MLflow are the <strong>main unit of organization</strong> for ML model training runs. All MLflow runs belong to an experiment.</p><p>An MLflow experiment can be created using either the <code>mlflow.create_experiment</code> or <code>mlflow.set_experiment</code>command. The latter is more commonly used, as it will activate an existing experiment by name or create a new one if it doesn't already exist.</p><p>It is possible to add tags to your experiment by passing them as a parameter to the <code>mlflow.create_experiment</code> command. If the experiment already exists, tags can be added using the <code>mlflow.set_experiment_tags</code> command after the experiment has been activated:</p><pre><code>experiment = mlflow.set_experiment(experiment_name="/Shared/marvel-demo")
mlflow.set_experiment_tags(
    {"repository_name": "marvelousmlops/marvel-characters"})</code></pre><p>The above commands will result in the creation of an instance of <strong>mlflow.entities.Experiment </strong>class with the following attributes:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0Oqg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Oqg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 424w, https://substackcdn.com/image/fetch/$s_!0Oqg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 848w, https://substackcdn.com/image/fetch/$s_!0Oqg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 1272w, https://substackcdn.com/image/fetch/$s_!0Oqg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Oqg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png" width="1400" height="474" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:474,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!0Oqg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 424w, https://substackcdn.com/image/fetch/$s_!0Oqg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 848w, https://substackcdn.com/image/fetch/$s_!0Oqg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 1272w, https://substackcdn.com/image/fetch/$s_!0Oqg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0361205a-6b5c-4f30-9550-6558835521ef_1400x474.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The MLflow experiment created above can be now found <em>by name</em> or <em>by tag:</em></p><pre><code># search experiment by name
mlflow.search_experiments(filter_string="name='/Shared/demo'")

# search experiment by tag
mlflow.search_experiments(
    filter_string="tags.repository_name='marvelousmlops/marvel-characters'")</code></pre><p>It is also possible to search for experiments by <em>last_update_time</em> or <em>creation time. </em>We can also get an experiment by experiment id:</p><pre><code>mlflow.get_experiment(experiment.experiment_id)</code></pre><h1><strong>MLflow Run</strong></h1><p>After the experiment is created, we can create an experiment run. An MLflow run is related to a single execution of ML model training code, under an MLflow run a lot of things can be logged: params, metrics, and artifacts of different formats.</p><p>An MLflow run is created using the <code>mlflow.start_run</code> command. This command is typically used within a <code>with</code> block to ensure proper handling of the run lifecycle. If you don&#8217;t use a <code>with</code> block, you must explicitly end the run by calling <code>mlflow.end_run</code>. Here, we log some metrics and a parameter:</p><pre><code>with mlflow.start_run(
    run_name="marvel-demo-run",
    tags={"git_sha": "1234567890abcd"},
    description="marvel character prediction demo run",
) as run:
    run_id = run.info.run_id
    mlflow.log_params({"type": "marvel_demo"})
    mlflow.log_metrics({"metric1": 1.0, "metric2": 2.0})</code></pre><p>The code above will result in the creation of an instance of <strong>mlflow.entities.Run </strong>class. It&#8217;s easy to visualize how the MLflow run class is constructed by saving the run information as a JSON file and using JSON Lens:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JamI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JamI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 424w, https://substackcdn.com/image/fetch/$s_!JamI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 848w, https://substackcdn.com/image/fetch/$s_!JamI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 1272w, https://substackcdn.com/image/fetch/$s_!JamI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JamI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png" width="1400" height="931" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:931,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JamI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 424w, https://substackcdn.com/image/fetch/$s_!JamI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 848w, https://substackcdn.com/image/fetch/$s_!JamI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 1272w, https://substackcdn.com/image/fetch/$s_!JamI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cd19d6-14dd-4e33-bb40-483c4c2aa672_1400x931.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can notice the git_sha tag that we passed to the run, and some tags were created by MLflow. The tags would be different depending on where this run was created. For example, if the run was created when running in a Lakeflow job, the job id and the task run id will be available as tags.</p><p>The run can be restarted by passing the run id to the mlflow.start_run() command. We can log extra information as part of the run, but we can&#8217;t overwrite existing metrics and parameters.</p><h2><strong>Logging and loading artifacts</strong></h2><p>Let&#8217;s now start a new run and log various artifacts:</p><pre><code>import matplotlib.pyplot as plt
import numpy as np

mlflow.start_run(run_name="marvel-demo-run-extra",
                 tags={"git_sha": "1234567890abcd"},
                       description="marvel demo run with extra artifacts",)
mlflow.log_metric(key="metric3", value=3.0)
# dynamically log metric (trainings epochs)
for i in range(0,3):
    mlflow.log_metric(key="metric1", value=3.0+i/2, step=i)
mlflow.log_artifact("../demo_artifacts/mlflow_meme.jpeg")
mlflow.log_text("hello, MLflow!", "hello.txt")
mlflow.log_dict({"k": "v"}, "dict_example.json")
mlflow.log_artifacts("../demo_artifacts", artifact_path="demo_artifacts")

# log figure
fig, ax = plt.subplots()
ax.plot([0, 1], [2, 3])

mlflow.log_figure(fig, "figure.png")

# log image dynamically
for i in range(0,3):
    image = np.random.randint(0, 256, size=(100, 100, 3), 
                              dtype=np.uint8)
    mlflow.log_image(image, key="demo_image", step=i)

mlflow.end_run()</code></pre><p>We can view the experiment run in the UI and find all the artifacts under the Artifacts tab:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0mRM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0mRM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 424w, https://substackcdn.com/image/fetch/$s_!0mRM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 848w, https://substackcdn.com/image/fetch/$s_!0mRM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 1272w, https://substackcdn.com/image/fetch/$s_!0mRM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0mRM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png" width="1400" height="609" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aedd2d81-207f-492a-93b9-297a83420b45_1400x609.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:609,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!0mRM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 424w, https://substackcdn.com/image/fetch/$s_!0mRM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 848w, https://substackcdn.com/image/fetch/$s_!0mRM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 1272w, https://substackcdn.com/image/fetch/$s_!0mRM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faedd2d81-207f-492a-93b9-297a83420b45_1400x609.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can also search for the run programmatically. Notice that we can filter our search results based on tags, run name, metrics, status, and tags:</p><pre><code>from time import time

time_hour_ago = int(time() - 3600) * 1000

runs = mlflow.search_runs(
    search_all_experiments=True, #or experiment_ids=[], or experiment_names=[]
    order_by=["start_time DESC"],
    filter_string="status='FINISHED' AND "
                  f"start_time&gt;{time_hour_ago} AND "
                  "run_name LIKE '%marvel-demo-run%' AND "
                  "metrics.metric3&gt;0 AND "
                  "tags.mlflow.source.type!='JOB'"</code></pre><p>We can now load the artifacts using the appropriate MLflow load functions, and download all artifacts using the <code>download_artifacts()</code> function.</p><pre><code>artifact_uri = runs.artifact_uri[0]
mlflow.artifacts.load_dict(f"{artifact_uri}/dict_example.json")

mlflow.artifacts.load_image(f"{artifact_uri}/figure.png")

mlflow.artifacts.download_artifacts(
    artifact_uri=f"{artifact_uri}/demo_artifacts",
    dst_path="../downloaded_artifacts")</code></pre><p>To simplify your understanding of logging and loading artifacts in MLflow, we prepared a cheatsheet. In the next lecture, we&#8217;ll go through logging and registering models.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DGY4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DGY4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 424w, https://substackcdn.com/image/fetch/$s_!DGY4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 848w, https://substackcdn.com/image/fetch/$s_!DGY4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 1272w, https://substackcdn.com/image/fetch/$s_!DGY4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DGY4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png" width="1174" height="1262" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1262,&quot;width&quot;:1174,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!DGY4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 424w, https://substackcdn.com/image/fetch/$s_!DGY4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 848w, https://substackcdn.com/image/fetch/$s_!DGY4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 1272w, https://substackcdn.com/image/fetch/$s_!DGY4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe499a5e3-9d7b-4f98-b2be-0ef255fcd93c_1174x1262.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>If you are interested to go broader and dive deeper into the topic, get your code reviewed by us, and get constant feedback, follow our <a href="https://maven.com/marvelousmlops/mlops-with-databricks">End-to-end MLOps with Databricks course</a> on Maven.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bmxh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bmxh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 424w, https://substackcdn.com/image/fetch/$s_!bmxh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 848w, https://substackcdn.com/image/fetch/$s_!bmxh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 1272w, https://substackcdn.com/image/fetch/$s_!bmxh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bmxh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png" width="1400" height="540" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:540,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!bmxh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 424w, https://substackcdn.com/image/fetch/$s_!bmxh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 848w, https://substackcdn.com/image/fetch/$s_!bmxh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 1272w, https://substackcdn.com/image/fetch/$s_!bmxh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F955e2f91-af76-4af0-9221-f94d158e8062_1400x540.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Use code MARVELOUS for 100 euro off.</p><p>Temporary discount (until 10 August 2025): BDAY for 20% off.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Developing on Databricks]]></title><description><![CDATA[Lecture 2 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/developing-on-databricks</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/developing-on-databricks</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Tue, 29 Jul 2025 15:25:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/nEue_1THOvk" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on <strong>MLOps with Databricks</strong>.</p><p>This article is part of the course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks.</p><p>Let&#8217;s dive into lecture 2, where we talk about developing on Databricks. View the lecture on Marvelous MLOps YouTube channel:</p><div id="youtube2-nEue_1THOvk" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;nEue_1THOvk&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/nEue_1THOvk?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>Most people using Databricks start by developing directly in a Databricks notebook, because it&#8217;s easy, fast, and convenient. But when it comes to MLOps, that convenience can quickly become a bottleneck. Notebooks make it difficult to write modular code, apply proper code quality standards, or run unit tests, all of which are essential for maintainable, production-grade ML systems.</p><p>Fortunately, there&#8217;s a better way. <strong>Databricks developer tools,</strong> such as <strong>VS Code extension</strong>, <strong>Databricks CLI</strong>, and <strong>Databricks Connect, </strong>allow you to develop locally using modern engineering workflows, while still running your pyspark code on Databricks.</p><p>In this lecture, we&#8217;ll show you how to use these tools to move development outside notebooks and adopt workflows that align better with MLOps practices.</p><h1><strong>Getting started</strong></h1><p>To follow along with the course, you&#8217;ll need to set up a few things. In the video, we demonstrate how to walk through these steps.</p><p>1.<strong>Get the Databricks free edition. </strong>The course uses <a href="https://www.databricks.com/learn/free-edition">Databricks free edition</a>. Do not confuse it with Databricks free trial, it is not the same thing!</p><p>2.<strong>Fork the course repo:</strong> <a href="https://github.com/marvelousmlops/marvel-characters">https://github.com/marvelousmlops/marvel-characters</a>. Forking the repo will allow you to work on CI/CD pipeline later in the course. Clone the forked repo on your local machine</p><p>3.<strong>Create catalogs and schemas.</strong> In the Databricks free edition workspace, create <em><strong>catalogs mlops_dev, mlops_acc, and mlops_prd.</strong></em>Under each catalog, create <em><strong>schema marvel_characters.</strong></em> These catalogs and schemas are required to run the code.</p><p>4.<strong>Install the CLI. </strong>Databricks has very good documentation on how to install it: <a href="https://docs.databricks.com/en/dev-tools/cli/install.html">https://docs.databricks.com/en/dev-tools/cli/install.html</a>. From our experience, the homebrew option works great on MacOS. On Window &#8212; winget. Otherwise, you can always install from a source build.</p><p>5.<strong>Authenticate towards Databricks. </strong>Databricks CLI should be used to authenticate towards Databricks from your local machine.</p><p>We do not recommend using personal access tokens (from a security perspective, this is not the best option). Instead, use <a href="https://docs.databricks.com/en/dev-tools/cli/authentication.html#oauth-user-to-machine-u2m-authentication">user-to-machine authentication</a>. For the workspace-level commands, use the following command to authenticate:</p><pre><code><code>databricks auth login --host &lt;workspace-url&gt;</code></code></pre><p>It creates an authentication profile in .databrickscfg file that looks like this (this is not a real host, just an example):</p><pre><code><code>[dbc-1234a567-b8c9]
host      = https://dbc-1234a567-b8c9.cloud.databricks.com/
auth_type = databricks-cli</code></code></pre><p>6.<strong>Install Databricks VS Code extension: </strong>follow the <a href="https://docs.databricks.com/en/dev-tools/vscode-ext/install.html">official documentation</a>. Once installed, update the databricks.yml file to match the host of the Databricks Free edition workspace.</p><h1><strong>Local Python environment management</strong></h1><p>In this course, we use <a href="https://docs.astral.sh/uv/#installation">uv</a> for Python project management, and we highly recommend trying it out if you haven&#8217;t already.</p><p>The Python version and project dependencies are defined in the <code>pyproject.toml</code> file. You&#8217;ll notice that we specify <strong>exact versions</strong> for all packages. This is intentional, but only because the project is not meant to be reused as a library. For reusable packages, locking dependencies too strictly can lead to dependency resolution issues down the line.</p><p>In our case, the <code>marvel-characters</code> package is meant to be self-contained. When we install it inside a Databricks environment, we want these specific dependency versions to override the existing ones.</p><pre><code>[project]
name = "marvel-characters"
description = "Marvel characters project"
requires-python = "&gt;=3.12, &lt;3.13"
dependencies = [
    "mlflow==3.1.1",
    "cffi==1.17.1",
    "cloudpickle==3.1.1",
    "numpy==1.26.4",
    "pandas==2.3.0",
    "pyarrow==17.0.0",
    "scikit-learn==1.7.0",
    "lightgbm==4.6.0",
    "scipy==1.16.0",
    "databricks-sdk==0.55.0",
    "pydantic==2.11.7",
    "loguru==0.7.3",
    "python-dotenv==1.1.1",
]

dynamic = ['version']

[project.optional-dependencies]
dev = ["databricks-connect&gt;=16.3, &lt;17",
       "ipykernel&gt;=6.29.5, &lt;7",
       "pip&gt;=25.0.1, &lt;26",
       "pre-commit&gt;=4.1.0, &lt;5"]

ci = ["pre-commit&gt;=4.1.0, &lt;5"]</code></pre><p>For development purpose, we use dev optional dependency. To get started, you just need to run:</p><pre><code>uv sync --extra dev</code></pre><p>This command will build Python virtual environment, stored in .venv inside your project folder.</p><p>If you want to execute pyspark code on Databricks, you need to choose the cluster in the VS Code extension UI. In the Free edition that we have set up, you can only choose serverless compute. When working locally, our local Python version environment defines how the code runs. If you want to run the code in a Databricks notebook, you need to choose the corresponding serverless environment.</p><h1><strong>Serverless environments</strong></h1><p>Currently, Databricks supports three environment versions, each comes with its own requirements. you can view the <a href="https://docs.databricks.com/aws/en/release-notes/serverless/environment-version/">release notes</a> for more details.</p><p>What matters is that your project&#8217;s Python version must match the environment Python version. In our case, we use Python 3.12, which matches <a href="https://docs.databricks.com/aws/en/release-notes/serverless/environment-version/three">environment version 3</a>, which comes with the following system settings:</p><ul><li><p>Operating System: Ubuntu 24.04.2 LTS</p></li><li><p>Python: 3.12.3</p></li><li><p>Databricks Connect: 16.3.2</p></li></ul><p>It also has certain versions of Python packages installed. Note that we are not using those package versions. For example, we use MLflow 3.1.1 instead of 2.21.3.</p><p>To see how to choose the environment version in a Databricks notebook and install the project&#8217;s dependencies, you can use Git Folder functionality. <a href="https://docs.databricks.com/aws/en/repos/git-operations-with-repos">Databricks documentation</a> walks you through the process of creating the Git folder.</p><p>After the Git folder is created, go to the notebooks folder of the project. Note that all the files inside the notebooks folder are .py files, with the first line &#8220;# Databricks notebook source&#8221;, and cells delimited by &#8220;# COMMAND &#8212; &#8212; &#8212; &#8212; &#8212; &#8221;.</p><p>Open the <code>lecture2.marvel_data_preprocessing.py</code> notebook. To select an environment version: click the Environment side panel in the notebook UI, select version 3, and click apply.</p><p>To install the project dependencies, run:</p><pre><code>%pip install -e ..
%restart_python</code></pre><p>The project code will not be importable unless you adjust the path (which should be avoided in any other scenario).</p><pre><code>from pathlib import Path
import sys
sys.path.append(str(Path.cwd().parent / 'src'))</code></pre><p>Instead of using Git Folder, you can also syncronize your project folder to a location in the workspace using the sync button under &#8220;Remote Folder&#8221; in the Databricks VS Code extension.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nOWC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nOWC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 424w, https://substackcdn.com/image/fetch/$s_!nOWC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 848w, https://substackcdn.com/image/fetch/$s_!nOWC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 1272w, https://substackcdn.com/image/fetch/$s_!nOWC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nOWC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png" width="716" height="530" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:530,&quot;width&quot;:716,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!nOWC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 424w, https://substackcdn.com/image/fetch/$s_!nOWC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 848w, https://substackcdn.com/image/fetch/$s_!nOWC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 1272w, https://substackcdn.com/image/fetch/$s_!nOWC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5431640f-9a18-4a32-bade-d20e55397a1e_716x530.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This allows to install package in a nicer way: you can build the package locally using uv build. If the resulting wheel file is not ignored by .gitignore, it will be synced together with other files, allowing to install the wheel directly within the notebook.</p><p>Now you can run the project code in a Databricks notebook using the serverless environment 3 and our project&#8217;s dependencies. We can also execute it directly in the VS code. Let&#8217;s take a look at the code we are executing in a detail.</p><h1><strong>Project code</strong></h1><p>In this course, we are using the <a href="https://www.kaggle.com/datasets/mohitbansal31s/marvel-characters">Marvel Characters dataset</a> from Kaggle. Later in the course, we are going to use the character&#8217;s features to predict whether the character is going to survive. All the features, along with the model parameters, experiment names, and schema/ catalogs used in the projects, are stored in the <code>project_config_marvel.yml</code> file:</p><pre><code>prd:
  catalog_name: mlops_prd
  schema_name: marvel_characters
acc:
  catalog_name: mlops_acc
  schema_name: marvel_characters
dev:
  catalog_name: mlops_dev
  schema_name: marvel_characters


experiment_name_basic: /Shared/marvel-characters-basic
experiment_name_custom: /Shared/marvel-characters-custom

parameters:
  learning_rate: 0.01
  n_estimators: 1000
  max_depth: 6


num_features:
  - Height
  - Weight
  
cat_features:
  - Universe
  - Identity
  - Gender
  - Marital_Status
  - Teams
  - Origin
  - Magic
  - Mutant

target: Alive</code></pre><p>On purpose, we keep project configurations in a separate file, to make adjustments easily when needed.</p><p>Throughout the prokject, we use a <code>ProjectConfig</code> class to manage and validate project-level configuration. The configuration is validated using the <strong>pydantic</strong> library, which ensures correct data types and structure at runtime.</p><pre><code>from typing import Any

import yaml
from pydantic import BaseModel


class ProjectConfig(BaseModel):
    """Represent project configuration parameters loaded from YAML.

    Handles feature specifications, catalog details, and experiment parameters.
    Supports environment-specific configuration overrides.
    """

    num_features: list[str]
    cat_features: list[str]
    target: str
    catalog_name: str
    schema_name: str
    parameters: dict[str, Any]
    experiment_name_basic: str | None
    experiment_name_custom: str | None

    @classmethod
    def from_yaml(cls, config_path: str, env: str = "dev") -&gt; "ProjectConfig":
        """Load and parse configuration settings from a YAML file.

        :param config_path: Path to the YAML configuration file
        :param env: Environment name to load environment-specific settings
        :return: ProjectConfig instance initialized with parsed configuration
        """
        if env not in ["prd", "acc", "dev"]:
            raise ValueError(f"Invalid environment: {env}. Expected 'prd', 'acc', or 'dev'")

        with open(config_path) as f:
            config_dict = yaml.safe_load(f)
            config_dict["catalog_name"] = config_dict[env]["catalog_name"]
            config_dict["schema_name"] = config_dict[env]["schema_name"]

            return cls(**config_dict)</code></pre><p>The <code>lecture2.marvel_data_preprocessing.py</code> notebook also starts with loading the configuration:</p><pre><code>import pandas as pd
import yaml
from loguru import logger
from pyspark.sql import SparkSession

from marvel_characters.config import ProjectConfig
from marvel_characters.data_processor import DataProcessor

config = ProjectConfig.from_yaml(config_path="../project_config_marvel.yml", env="dev")

logger.info("Configuration loaded:")
logger.info(yaml.dump(config, default_flow_style=False))</code></pre><p>After loading the configuration, we initialize a <code>SparkSession</code>. Note that <code>SparkSession</code> is imported from the pyspark library, but we don&#8217;t have pyspark as a project dependency. That&#8217;s because we're using databricks-connect, which includes pyspark internally. This is why these two libraries cannot be installed side by side as they would conflict with each other.</p><p>Next, we load the dataset from the <code>data/</code> folder and create an instance of the <code>DataProcessor</code> class. This class is responsible for preprocessing the data, splitting it into training and test sets, and writing the processed data to Unity Catalog. This ensures traceability and makes it easy to reference the datasets consistently across environments.</p><p>Finally, we enable change data feed on the resulting Delta tables, which allows you to track row-level changes between versions:</p><pre><code>spark = SparkSession.builder.getOrCreate()

df = pd.read_csv("../data/marvel_characters_dataset.csv")

data_processor = DataProcessor(df, config, spark)
data_processor.preprocess()

X_train, X_test = data_processor.split_data()
data_processor.save_to_catalog(X_train, X_test)
data_processor.enable_change_data_feed()</code></pre><p>The preprocess() method performs data cleaning and normalization to prepare the Marvel character dataset for training. It includes the following steps:</p><ul><li><p>Column renaming for consistency</p></li><li><p>Filling missing values in categorical columns like Universe, Origin, Identity, and Gender</p></li><li><p>Grouping rare Universe categories under &#8220;Other&#8221;</p></li><li><p>Converting the Teams column to a binary indicator based on whether the value is present</p></li><li><p>Creating new binary features such as Magic and Mutant by detecting keywords in the Origin column</p></li><li><p>Normalizing values in the Origin column into a fixed set of categories like Human, Mutant, Asgardian, Alien, etc.</p></li><li><p>Converting the Alive column into a binary target variable and filtering only valid values</p></li><li><p>Keeping only the selected numeric and categorical features, along with the target and PageID (which is renamed to Id)</p></li></ul><p>This preprocessing step produces a clean, well-structured dataset that&#8217;s ready for model training. We&#8217;ll use it in Lecture 4, where we focus on training, logging, and registering a model. But before that, in the next lecture, we&#8217;ll explore MLflow experiment tracking, the foundation of everything that happens within MLflow.</p><div><hr></div><p>If you are interested to go broader and dive deeper into the topic, get your code reviewed by us, and get constant feedback, follow our <a href="https://maven.com/marvelousmlops/mlops-with-databricks">End-to-end MLOps with Databricks course</a> on Maven.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0Zlp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Zlp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 424w, https://substackcdn.com/image/fetch/$s_!0Zlp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 848w, https://substackcdn.com/image/fetch/$s_!0Zlp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 1272w, https://substackcdn.com/image/fetch/$s_!0Zlp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Zlp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png" width="1400" height="540" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:540,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!0Zlp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 424w, https://substackcdn.com/image/fetch/$s_!0Zlp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 848w, https://substackcdn.com/image/fetch/$s_!0Zlp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 1272w, https://substackcdn.com/image/fetch/$s_!0Zlp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ba21378-2fad-4e4b-857c-e384a0d50678_1400x540.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Use code MARVELOUS for 100 euro off.</p><p>Temporary discount (until 10 August 2025): BDAY for 20% off.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Introduction to MLOps]]></title><description><![CDATA[Lecture 1 of MLOps with Databricks course]]></description><link>https://www.marvelousmlops.io/p/introduction-to-mlops</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/introduction-to-mlops</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Mon, 28 Jul 2025 17:19:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/gqrl4QpfHzo" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Databricks recently introduced <strong>Free Edition</strong>, which opened the door for us to create a free hands-on course on <strong>MLOps with Databricks</strong>.</p><p>This article is part of the course series, where we walk through the tools, patterns, and best practices for building and deploying machine learning workflows on Databricks :</p><ul><li><p>Lecture 1: Introduction to MLOPs</p></li><li><p>Lecture 2: Developing on Databricks</p></li><li><p>Lecture 3: Getting started with MLflow</p></li><li><p>Lecture 4: Log and register model with MLflow</p></li><li><p>Lecture 5: Model serving architectures</p></li><li><p>Lecture 6: Deploying model serving endpoint</p></li><li><p>Lecture 7: Databricks Asset Bundles</p></li><li><p>Lecture 8: CI/CD and deployment strategies</p></li><li><p>Lecture 9: Intro to monitoring</p></li><li><p>Lecture 10: Lakehouse monitoring</p></li></ul><p>Let&#8217;s dive into lecture 1. View the lecture recording on Marvelous MLOps YouTube: </p><div id="youtube2-gqrl4QpfHzo" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;gqrl4QpfHzo&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/gqrl4QpfHzo?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>If you&#8217;ve worked with machine learning in a real-world setting, you&#8217;ve likely heard the term <strong>MLOps</strong>. According to Wikipedia:</p><blockquote><p><em><strong>&#8220;MLOps is a paradigm that aims to deploy and maintain machine learning models in production reliably and efficiently.&#8221;</strong></em></p></blockquote><p>But what does <em>production</em> actually mean in this context?</p><p>In practice, production means that the output of a machine learning model is consistently delivered to end users or systems ,and that it drives real business value.</p><p>Here&#8217;s a simple example:</p><p>A data scientist is asked to build a demand forecasting model. They develop a proof of concept in a Databricks notebook, more than a thousand lines of code, that trains a model, generates predictions, and writes those predictions to a Delta table.</p><p>That Delta table is then used by the fulfillment team to order products. Since forecasts need to be updated weekly, the data scientist schedules the notebook to run once a week.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9Emt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9Emt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 424w, https://substackcdn.com/image/fetch/$s_!9Emt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 848w, https://substackcdn.com/image/fetch/$s_!9Emt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 1272w, https://substackcdn.com/image/fetch/$s_!9Emt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9Emt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png" width="1400" height="780" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:780,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9Emt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 424w, https://substackcdn.com/image/fetch/$s_!9Emt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 848w, https://substackcdn.com/image/fetch/$s_!9Emt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 1272w, https://substackcdn.com/image/fetch/$s_!9Emt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F258e5760-a6c5-4404-a5f8-fa5e665d82c9_1400x780.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Is this in production?</em><br><strong>Yes.</strong> The model&#8217;s outputs are actively used to support business decisions.<br><em>Is it efficient?</em><br><strong>To some extent.</strong> Automating the process with a scheduled notebook is certainly more efficient than running everything manually.<br><em>Is it reliable?</em><br><strong>Not really.</strong></p><p>This setup lacks testing, monitoring, error handling, version control, and formal deployment processes. If anything breaks, the data, the code, or the environment, there may be no alert, no rollback, and no audit trail. To address these gaps, we turn to <strong>MLOps principles, </strong>a set of practices designed to bring reliability, transparency, and control to machine learning workflows.</p><h3>MLOps principles</h3><p>Done right, MLOps enables teams to move faster without sacrificing stability or accountability.</p><p><strong>Traceability and reproducibility </strong>is the main principle in MLOps. For any ML model deployment or run, we should be able to look up unumbibuously:</p><ul><li><p>Corresponding code/ commit on git.</p></li><li><p>Infrastructure/ environment used for training and serving.</p></li><li><p>Data used for model training.</p></li><li><p>Generated ML model artifacts.</p></li></ul><p>This is just one set of MLOps principles. In practice, there are several other core pillars that contribute to building robust, maintainable, and collaborative machine learning systems:</p><ul><li><p><strong>Documentation:</strong> Every ML project should include clear documentation outlining business goals, KPIs, architectural decisions, and model selection rationale. Good documentation ensures continuity, helping onboard new team members, supporting handovers when someone leaves, and enabling other teams to understand how to integrate with the system.</p></li><li><p><strong>Code Quality:</strong> Maintaining high code quality is essential for stability and collaboration. This includes enforcing code reviews on all pull requests, adhering to style and structure conventions, and incorporating automated testing (unit, integration, and ideally ML-specific tests). A clean, testable codebase makes experimentation safer and deployments more reliable.</p></li><li><p><strong>Monitoring:</strong> In ML projects, monitoring extends beyond system metrics to include model performance. Offline evaluation metrics should be logged and tracked over time. In production, business KPIs and model-related costs must also be monitored. This provides visibility into how the model behaves in the real world and helps detect unintended outcomes early.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lNBK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lNBK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 424w, https://substackcdn.com/image/fetch/$s_!lNBK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 848w, https://substackcdn.com/image/fetch/$s_!lNBK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 1272w, https://substackcdn.com/image/fetch/$s_!lNBK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lNBK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png" width="1400" height="431" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:431,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lNBK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 424w, https://substackcdn.com/image/fetch/$s_!lNBK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 848w, https://substackcdn.com/image/fetch/$s_!lNBK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 1272w, https://substackcdn.com/image/fetch/$s_!lNBK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0941a07-4058-4e9e-a00f-9bcca9b916f1_1400x431.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>These principles, when applied consistently, form the foundation of a healthy ML project, which not only delivers value now, but remains stable and adaptable as teams, data, and requirements evolve.</p><h3>MLOps tooling</h3><p>Following MLOps principles isn&#8217;t feasible without the right tooling. While it might initially seem like you need dozens of different tools, the truth is: you need <strong>categories</strong> of tools, not necessarily specific ones. And chances are, your organization already has many of them in place.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P047!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P047!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 424w, https://substackcdn.com/image/fetch/$s_!P047!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 848w, https://substackcdn.com/image/fetch/$s_!P047!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 1272w, https://substackcdn.com/image/fetch/$s_!P047!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P047!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png" width="1272" height="666" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:666,&quot;width&quot;:1272,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!P047!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 424w, https://substackcdn.com/image/fetch/$s_!P047!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 848w, https://substackcdn.com/image/fetch/$s_!P047!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 1272w, https://substackcdn.com/image/fetch/$s_!P047!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31def7ce-832c-4455-9c27-ea97facc2e17_1272x666.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s a breakdown of the essential categories:</p><ul><li><p><strong>Version Control: </strong>Tools like GitHub, GitLab, Bitbucket are the foundation of reproducibility and collaboration in any ML project.</p></li><li><p><strong>CI/CD: </strong>Jenkins, GitHub Actions, GitLab CI/CD, and Azure DevOps help automate testing, packaging, and deployment of ML code and artifacts.</p></li><li><p><strong>Orchestration: </strong>Apache Airflow, Argo Workflows, or Lakeflow jobs coordinate complex multi-step ML workflows.</p></li><li><p><strong>Model Registry: </strong>Platforms like MLflow or Comet track model versions and metadata.</p></li><li><p><strong>Feature Stores or data version control tools: </strong>tools like Feast, Hopsworks, or Databricks&#8217; built-in feature store ensure consistent feature definitions across training and inference.</p></li><li><p><strong>Compute &amp; Serving: w</strong>hether you&#8217;re running training jobs on Databricks, SageMaker, or Kubernetes, or serving models via REST endpoints, your infrastructure must be able to support both development and production needs.</p></li><li><p><strong>Monitoring: </strong>most machine learning projects require custom metrics to monitor, along with the standard application performance metrics, which allows to use common observability tools like Prometheus and Grafana or ELK stack.</p></li><li><p><strong>Container Registry: t</strong>ools like DockerHub, Azure Container Registry, or AWS ECR are used to store and manage Docker images for consistent packaging and deployment of ML applications</p></li></ul><p>In MLOps, each tool is there to support MLOps principles. Focus must be on covering essential capabilities, not collecting tools.</p><p>Here&#8217;s what each tool category enables:</p><ul><li><p><strong>Version control, orchestration, CI/CD: </strong>Code reproducibility and traceability, and code quality.</p></li><li><p><strong>Model registry: </strong>Model traceability and reproducibility.</p></li><li><p><strong>Feature store, data versioning: </strong>Data traceability and reproducibility.</p></li><li><p><strong>Container registry: </strong>Environment traceability and reproducibility</p></li></ul><p>A simple, purpose-driven stack is all you need to do MLOps well.</p><h3>Is not it all just DevOps?</h3><p>At first glance, the tools and principles of MLOps might seem identical to DevOps, and in many ways, they are. MLOps builds on DevOps but adapts it to the unique challenges of machine learning.</p><p>DevOps aims to break the wall of confusion between development and IT operations to enable faster, more reliable delivery. MLOps shares that goal, but the wall is thicker and taller. On one side, you have ML teams focused on experimentation and modeling; on the other, platform teams focused on infrastructure and reliability. These groups often speak different technical languages. ML teams lack operations experience, and DevOps teams may not understand the specifics of ML systems.</p><p>This disconnect makes the development and deployment cycle for ML systems even more challenging, and that&#8217;s exactly the gap MLOps is designed to close.</p><p>But there&#8217;s a deeper difference. In traditional software systems, consistent code, infrastructure, and environment yield consistent results. In ML systems, a change in the <strong>statistical properties of the data</strong> alone can lead to different behavior. That&#8217;s why data monitoring and retraining aren&#8217;t just operational extras, they&#8217;re core to doing MLOps right.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qd6k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qd6k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 424w, https://substackcdn.com/image/fetch/$s_!qd6k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 848w, https://substackcdn.com/image/fetch/$s_!qd6k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 1272w, https://substackcdn.com/image/fetch/$s_!qd6k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qd6k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png" width="1400" height="848" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:848,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qd6k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 424w, https://substackcdn.com/image/fetch/$s_!qd6k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 848w, https://substackcdn.com/image/fetch/$s_!qd6k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 1272w, https://substackcdn.com/image/fetch/$s_!qd6k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf691abb-192b-4803-9490-8c31a00e1b97_1400x848.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>MLOps on Databricks</h3><p>MLOps isn&#8217;t about tools, it&#8217;s about principles. But you do need the right tools to follow those principles effectively. A few years ago, I might have recommended building your own platform. But today, the landscape has changed.</p><p>We&#8217;re in the middle of a tooling explosion in data and AI. There are more specialized solutions than ever before, but managing them can quickly become overwhelming. Databricks now brings together all the essential components for MLOps in one platform. While each individual part may not be the absolute best on the market, the value lies in having everything tightly integrated and ready to use.</p><p>Here&#8217;s how Databricks supports key MLOps functions:</p><ul><li><p><strong>Lakeflow Jobs</strong> for orchestration</p></li><li><p><strong>MLflow</strong> for experiment tracking and <strong>Unity Catalog</strong> for model registry</p></li><li><p><strong>Delta Tables</strong> for data versioning, also powering feature tables</p></li><li><p><strong>Databricks Compute</strong> for model training</p></li><li><p><strong>Feature Serving</strong> and <strong>Model Serving</strong> for real-time inference</p></li><li><p><strong>Lakehouse Monitoring</strong> for detecting data and model drift</p></li><li><p><strong>Databricks Asset Bundles</strong> to streamline CI/CD and deployment</p></li></ul><p>With these components in place, you can focus on building reliable ML systems, without stitching together a dozen tools yourself.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RPsM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RPsM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 424w, https://substackcdn.com/image/fetch/$s_!RPsM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 848w, https://substackcdn.com/image/fetch/$s_!RPsM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 1272w, https://substackcdn.com/image/fetch/$s_!RPsM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RPsM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png" width="1400" height="791" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:791,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RPsM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 424w, https://substackcdn.com/image/fetch/$s_!RPsM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 848w, https://substackcdn.com/image/fetch/$s_!RPsM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 1272w, https://substackcdn.com/image/fetch/$s_!RPsM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8454dc87-93ba-4fe8-ae29-83f1d3e437c6_1400x791.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this course, we&#8217;ll deep dive into all these features and explain how they can be used together to promote traceability, reproducibility, and monitoring standards.</p><div><hr></div><p>If you are interested to go broader and dive deeper into the topic, get your code reviewed by us, and get constant feedback, follow our <a href="https://maven.com/marvelousmlops/mlops-with-databricks">End-to-end MLOps with Databricks course</a> on Maven.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R3eh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R3eh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 424w, https://substackcdn.com/image/fetch/$s_!R3eh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 848w, https://substackcdn.com/image/fetch/$s_!R3eh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 1272w, https://substackcdn.com/image/fetch/$s_!R3eh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R3eh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png" width="1400" height="540" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:540,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R3eh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 424w, https://substackcdn.com/image/fetch/$s_!R3eh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 848w, https://substackcdn.com/image/fetch/$s_!R3eh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 1272w, https://substackcdn.com/image/fetch/$s_!R3eh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce37ab37-3bd7-485d-8ddd-6ab3f3c805bc_1400x540.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Use code MARVELOUS for 100 euro off.</p><p>Temporary discount (until 10 August 2025): BDAY for 20% off.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Stop Building AI agents]]></title><description><![CDATA[Maria: Today, the scene is owned by Hugo, a brilliant mind who advises and teaches teams building LLM-powered systems, including engineers from Netflix, Meta, and the U.S.]]></description><link>https://www.marvelousmlops.io/p/stop-building-ai-agents</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/stop-building-ai-agents</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Mon, 07 Jul 2025 10:59:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PB4r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Maria: </strong>Today, the scene is owned by Hugo, a brilliant mind who <em>advises and teaches teams building LLM-powered systems, including engineers from Netflix, Meta, and the U.S. Air Force.</em></p><p><em>He runs a course on <a href="https://maven.com/hugo-stefan/building-llm-apps-ds-and-swe-from-first-principles?promoCode=MARVELOUS10">the LLM software development lifecycle</a> (I am joining the July cohort!), focusing on everything from retrieval and evaluation to agent design, and all the intermediate steps in between.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Enough talking, I&#8217;ll let him dig into today&#8217;s controversial topic: <em>&#8220;Stop building AI agents&#8221;.</em> &#8595;&#127897;&#65039;</p><div><hr></div><p><strong>Hugo: </strong>I&#8217;ve taught and advised dozens of teams building LLM-powered systems. There&#8217;s a common pattern I keep seeing, and honestly, it&#8217;s frustrating.</p><p>Everyone reaches for agents first. They set up memory systems. They add routing logic. They create tool definitions and character backstories. It feels powerful, and it feels like progress.</p><p>Until everything breaks. And when things go wrong (which they always do), nobody can figure out why.</p><p><strong>Was it the agent forgetting its task? Is the wrong tool getting selected? Too many moving parts to debug? Is the whole system fundamentally brittle?</strong></p><p>I learned this the hard way. Six months ago, I built a &#8220;research crew&#8221; with CrewAI: three agents, five tools, perfect coordination on paper. But in practice? The researcher ignored the web scraper, the summarizer forgot to use the citation tool, and the coordinator gave up entirely when processing longer documents. It was a beautiful plan falling apart in spectacular ways.</p><p>This flowchart came from one of my lessons after debugging countless broken agent systems. Notice that tiny box at the end? That&#8217;s how rarely you actually need agents. Yet everyone starts there.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PB4r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PB4r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 424w, https://substackcdn.com/image/fetch/$s_!PB4r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 848w, https://substackcdn.com/image/fetch/$s_!PB4r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!PB4r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PB4r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg" width="1400" height="687" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:687,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!PB4r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 424w, https://substackcdn.com/image/fetch/$s_!PB4r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 848w, https://substackcdn.com/image/fetch/$s_!PB4r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!PB4r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eb69532-5c9a-4158-9f79-0722f1744949_1400x687.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This post is about what I learned from those failures, including how to avoid them entirely.</p><p>The patterns I&#8217;ll walk through are inspired by <a href="https://www.anthropic.com/engineering/building-effective-agents">Anthropic&#8217;s Building Effective Agents post</a>. It&#8217;s not just theory. This is real code, real failures, and real decisions I&#8217;ve made while teaching these systems. Every example here comes from actual projects I&#8217;ve built or debugged.</p><p>You&#8217;ll discover why agents aren&#8217;t the answer (most of the time). And more importantly, you&#8217;ll learn what to build instead.</p><p><strong>What You&#8217;ll Learn:</strong></p><ul><li><p>Why agents are usually not the right first step</p></li><li><p>Five LLM workflow patterns that solve most problems</p></li><li><p>When agents are the right tool and how to build them safely</p></li></ul><p><em>&#128279; All examples come from <a href="https://github.com/hugobowne/building-with-ai/blob/main/notebooks/01-agentic-continuum.ipynb">this GitHub notebook</a></em></p><h1><strong>Don&#8217;t Start with Agents</strong></h1><p>Everyone thinks agents are where you start. It&#8217;s not their fault: frameworks make it seem easy, demo videos are exciting, and tech Twitter loves the hype.</p><p>But here&#8217;s what I learned after building that CrewAI research crew: <strong>most agent systems break down from too much complexity, not too little.</strong></p><p>In my demo, I had three agents working together:</p><ul><li><p>A researcher agent that could browse web pages</p></li><li><p>A summarizer agent with access to citation tools</p></li><li><p>A coordinator agent that managed task delegation</p></li></ul><p>Pretty standard stuff, right? Except in practice:</p><ul><li><p>The researcher ignored the web scraper 70% of the time</p></li><li><p>The summarizer completely forgot to use citations when processing long documents</p></li><li><p>The coordinator threw up its hands when tasks weren&#8217;t clearly defined</p></li></ul><p>So wait: <em>&#8220;What exactly is an agent?&#8221;</em> To answer that, we need to look at 4 characteristics of LLM systems.</p><p>When people say &#8220;agent,&#8221; they mean that last step: the LLM output controls the workflow. Most people skip straight to letting the LLM control the workflow without realizing that <strong>simpler patterns often work better</strong>. Using an agent means handing control to the LLM. But unless your task is so dynamic that its flow can&#8217;t be defined upfront, that kind of freedom usually hurts more than it helps. Most of the time, simpler workflows with humans in charge still outperform full-blown agents.</p><p>I&#8217;ve debugged this exact pattern with dozens of teams:</p><ol><li><p>We have multiple tasks that need automation</p></li><li><p>Agents seem like the obvious solution</p></li><li><p>We build complex systems with roles and memory</p></li><li><p>Everything breaks because coordination is harder than we thought</p></li><li><p>We realize simpler patterns would have worked better</p></li></ol><blockquote><p><em><strong>&#128270; Takeaway: </strong>Start with simpler workflows like chaining or routing unless you know you need memory, delegation, and planning.</em></p></blockquote><h1><strong>Workflow patterns you should use</strong></h1><p>These five patterns come from <a href="https://www.anthropic.com/engineering/building-effective-agents">Anthropic&#8217;s taxonomy</a> &#8212; <a href="https://github.com/hugobowne/building-with-ai/blob/main/notebooks/01-agentic-continuum.ipynb">implemented, tested, and demoed in my notebook</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ivq-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ivq-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 424w, https://substackcdn.com/image/fetch/$s_!ivq-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 848w, https://substackcdn.com/image/fetch/$s_!ivq-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 1272w, https://substackcdn.com/image/fetch/$s_!ivq-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ivq-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png" width="1400" height="583" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:583,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ivq-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 424w, https://substackcdn.com/image/fetch/$s_!ivq-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 848w, https://substackcdn.com/image/fetch/$s_!ivq-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 1272w, https://substackcdn.com/image/fetch/$s_!ivq-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b28f5d-0aba-4322-9c24-798b79abeb05_1400x583.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The prompt chaining workflow &#8212; Image from <strong><a href="https://www.anthropic.com/engineering/building-effective-agents">Building effective agents</a> </strong>by Anthropic</figcaption></figure></div><p><em>Use case: &#8220;Writing personalized outreach emails based on LinkedIn profiles.&#8221;</em></p><p>You want to reach out to people at companies you&#8217;re interested in. Start by extracting structured data from a LinkedIn profile (name, role, company), then generate a tailored outreach email to start a conversation.</p><p><strong>Here are 3 simple steps:</strong></p><ol><li><p>Turn raw LinkedIn profile text into structured data (e.g., name, title, company):</p></li></ol><pre><code>linkedin_data = extract_structured_data(raw_profile)</code></pre><p>2. Add relevant company context for personalization (e.g., mission, open roles):</p><pre><code>company_context = enrich_with_context(linkedin_data)</code></pre><p>3. Generate a personalized outreach email using the structured profile + company context:</p><pre><code>email = generate_outreach_email(linkedin_data, company_context)</code></pre><h2><strong>Guidelines:</strong></h2><p>&#9989; Use when: Tasks flow sequentially<br>&#9888;&#65039; Failure mode: Chain breaks if one step fails<br>&#128161; Simple to debug, predictable flow</p><h1><strong>(2) Parallelization</strong></h1><p><em>Use case: Extracting structured data from profiles</em></p><p>Now that chaining works, you want to process many profiles at once and speed up the processing. Split each profile into parts &#8212; like education, work history, and skills, then run extract_structured_data() in parallel.</p><p><strong>Here are 2 simple steps:</strong></p><ol><li><p>Define tasks to extract key profile fields in parallel:</p></li></ol><pre><code>tasks = [ extract_work_history(profile), # Pull out work experience details extract_skills(profile), # Identify listed skills extract_education(profile) # Parse education background ]</code></pre><p>2. Run all tasks concurrently and gather results:</p><pre><code>results = await asyncio.gather(*tasks)</code></pre><h2><strong>Guidelines:</strong></h2><p>&#9989; Use when: Independent tasks run faster concurrently<br>&#9888;&#65039; Failure mode: Race conditions, timeout issues<br>&#128161; Great for data extraction across multiple sources</p><h1><strong>(3) Routing</strong></h1><p><em>Use case: LLM classifies the input and sends it to a specialized workflow</em></p><p>Say you&#8217;re building a support tool that handles product questions, billing issues, and refund requests. Routing logic classifies each message and sends it to the right workflow. If it&#8217;s unclear, fall back to a generic handler.</p><p><strong>Here are 2 simple steps:</strong></p><ol><li><p>Choose a handler based on profile type:</p></li></ol><pre><code>if profile_type == "executive": handler = executive_handler() # Use specialized logic for executives elif profile_type == "recruiter": handler = recruiter_handler() # Use recruiter-specific processing else: handler = default_handler() # Fallback for unknown or generic profiles</code></pre><p>2. Process the profile with the selected handler:</p><pre><code>result = handler.process(profile)</code></pre><h2><strong>Guidelines:</strong></h2><p>&#9989; Use when: Different inputs need different handling<br>&#9888;&#65039; Failure mode: Edge cases fall through routes<br>&#128161; Add catch-all routes for unknowns</p><h1><strong>(4) Orchestrator-Worker</strong></h1><p><em>Use case: LLM breaks down the task into 1 or more dynamic steps</em></p><p>You&#8217;re generating outbound emails. The orchestrator classifies the target company as tech or non-tech, then delegates to a specialized worker that crafts the message for that context.</p><p><strong>Here are 2 simple steps:</strong></p><ol><li><p>Use LLM to classify the profile as tech or non-tech:</p></li></ol><pre><code>industry = llm_classify(profile_text)</code></pre><p>2. Route to the appropriate worker based on classification:</p><pre><code>if industry == "tech": email = tech_worker(profile_text, email_routes) else: email = non_tech_worker(profile_text, email_routes)</code></pre><p>The orchestrator-worker pattern separates decision-making from execution:</p><p>At first glance, this might resemble routing: a classifier picks a path, then a handler runs. But in routing, control is handed off entirely. In this example, the orchestrator retains control: it initiates the classification, selects the worker, and manages the flow from start to finish.</p><p>This is a minimal version of the orchestrator-worker pattern:</p><p>You can scale this up with multiple workers, sequential steps, or aggregation logic (and I encourage you to! If you do so, <a href="https://github.com/hugobowne/building-with-ai/blob/main/notebooks/01-agentic-continuum.ipynb">make a PR to the repository</a>), but the core structure stays the same.</p><h2><strong>Guidelines:</strong></h2><p>&#9989; Use when: Tasks need specialized handling<br>&#9888;&#65039; Failure mode: Orchestrator delegates subtasks poorly or breaks down the task incorrectly<br>&#128161; Keep orchestrator logic simple and explicit</p><h1><strong>(5) Evaluator-Optimizer</strong></h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5emf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5emf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 424w, https://substackcdn.com/image/fetch/$s_!5emf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 848w, https://substackcdn.com/image/fetch/$s_!5emf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 1272w, https://substackcdn.com/image/fetch/$s_!5emf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5emf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png" width="1400" height="583" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:583,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5emf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 424w, https://substackcdn.com/image/fetch/$s_!5emf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 848w, https://substackcdn.com/image/fetch/$s_!5emf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 1272w, https://substackcdn.com/image/fetch/$s_!5emf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8aaccbd-aca2-40fd-a6ed-3ad9a2d91af6_1400x583.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The evaluator-optimizer workflow &#8212; Image from <strong><a href="https://www.anthropic.com/engineering/building-effective-agents">Building effective agents</a> </strong>by<strong> </strong>Anthropic</figcaption></figure></div><p><em>Use case: Refining outreach emails to better match your criteria</em></p><p>You&#8217;ve got an email generator running, but want to improve tone, structure, or alignment. Add an evaluator that scores each message and, If it doesn&#8217;t pass, send it back to the generator with feedback and loop until it meets your bar.</p><p><strong>Here are 2 simple steps:</strong></p><ol><li><p>Generate an initial email from the profile:</p></li></ol><pre><code>content = generate_email(profile)</code></pre><p>2. Loop until the email passes the evaluator or hits a retry limit:</p><pre><code>while True: score = evaluate_email(content) if score.overall &gt; 0.8 or score.iterations &gt; 3: break content = optimize_email(content, score.feedback)</code></pre><h2><strong>Guidelines:</strong></h2><p>&#9989; Use when: Output quality matters more than speed<br>&#9888;&#65039; Failure mode: Infinite optimization loops<br>&#128161; Set clear stop conditions</p><blockquote><p><em><strong>&#128270; Takeaway: </strong>Most use cases don&#8217;t need agents. They need better workflow structure.</em></p></blockquote><h1><strong>When to Use Agents (If You Really Have To)</strong></h1><p>Agents shine when you have a sharp human in the loop. Here&#8217;s my hot take: agents excel at unstable workflows where human oversight can catch and correct mistakes.</p><p><em>When agents actually work well:</em></p><h2><strong>Example 1: Data Science Assistant</strong></h2><p>An agent that writes SQL queries, generates visualizations, and suggests analyses. You&#8217;re there to evaluate results and fix logical errors. The agent&#8217;s creativity in exploring data beats rigid workflows.</p><p>To build something like this, you&#8217;d give the LLM access to tools like run_sql_query(), plot_data(), and summarize_insights(). The agent routes between them based on the user&#8217;s request &#8212; for example, writing a query, running it, visualizing the result, and generating a narrative summary. Then, it feeds the result of each tool call back into another LLM request with its memory context. We walk through a live example of this pattern in our <a href="https://maven.com/hugo-stefan/building-llm-apps-ds-and-swe-from-first-principles?promoCode=MARVELOUS10">Building with LLMs course</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lAMp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lAMp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lAMp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lAMp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lAMp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lAMp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg" width="1400" height="716" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!lAMp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 424w, https://substackcdn.com/image/fetch/$s_!lAMp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 848w, https://substackcdn.com/image/fetch/$s_!lAMp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!lAMp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff9b45dd-5eeb-40f9-b248-1d5e5f47bd65_1400x716.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Example 2: Creative Writing Partner</strong></h2><p>An agent brainstorming headlines, editing copy, and suggesting structures. The human judges quality and redirects when needed. Agents excel at ideation with human judgment.</p><h2><strong>Example 3: Code Refactoring Assistant</strong></h2><p>Proposing design patterns, catching edge cases, and suggesting optimizations. The developer reviews and approves changes. Agents spot patterns humans miss.</p><h1><strong>When NOT to use agents</strong></h1><p><strong>Enterprise Automation</strong><br>Building stable, reliable software? Don&#8217;t use agents. You can&#8217;t have an LLM deciding critical workflows in production. Use orchestrator patterns instead.</p><p>Back to my CrewAI research crew: the agents kept forgetting goals and skipping tools. Here&#8217;s what I learned:</p><p><strong>Failure Point #1:</strong> Agents assumed they had context that they didn&#8217;t <br><strong>Problem:</strong> Long documents caused the summarizer to forget citations entirely <br><strong>What I&#8217;d do now:</strong> Use explicit memory systems, not just role prompts</p><p><strong>Failure Point #2:</strong> Agents failed to select the right tools <br><strong>Problem:</strong> The researcher ignored the web scraper in favor of a general search <br><strong>What I&#8217;d do now:</strong> Constrain choices with explicit tool menus</p><p><strong>Failure Point #3:</strong> Agents did not handle coordination well <br><strong>Problem:</strong> The coordinator gave up when tasks weren&#8217;t clearly scoped <br><strong>What I&#8217;d do now:</strong> Build explicit handoff protocols, not free-form delegation</p><blockquote><p><em><strong>&#128270; Takeaway:</strong> If you&#8217;re building agents, treat them like full software systems. Don&#8217;t skip observability.</em></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!po4S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!po4S!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 424w, https://substackcdn.com/image/fetch/$s_!po4S!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 848w, https://substackcdn.com/image/fetch/$s_!po4S!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!po4S!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!po4S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg" width="1400" height="464" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:464,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!po4S!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 424w, https://substackcdn.com/image/fetch/$s_!po4S!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 848w, https://substackcdn.com/image/fetch/$s_!po4S!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!po4S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2edcb95a-3007-43d3-abeb-0886cf22f7cd_1400x464.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p>&#10060; Agents are overhyped and overused</p></li><li><p>&#128257; Most cases need simple patterns, not agents</p></li><li><p>&#129309; Agents excel in human-in-the-loop scenarios</p></li><li><p>&#9888;&#65039; Don&#8217;t use agents for stable enterprise systems</p></li><li><p>&#129514; Build with observability and explicit control</p></li></ul><p>Agents are overhyped and often overused. In most real-world applications, simple patterns and direct API calls work better than complex agent frameworks. Agents do have a role-in particular, they shine in human-in-the-loop scenarios where oversight and flexibility are needed. But for stable enterprise systems, they introduce unnecessary complexity and risk. Instead, aim to build with strong observability, clear evaluation loops, and explicit control.</p><p><em>Want to go deeper?</em> I teach <strong><a href="https://maven.com/hugo-stefan/building-llm-apps-ds-and-swe-from-first-principles?promoCode=MARVELOUS10">a course on the entire LLM software development lifecycle</a> </strong>(use <strong>code MARVELOUS10</strong> for 10%<strong> off</strong>), covering everything from retrieval and evaluation to observability, agents, and production workflows. It&#8217;s designed for engineers and teams who want to move fast without getting stuck in proof of concept purgatory.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K9if!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K9if!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 424w, https://substackcdn.com/image/fetch/$s_!K9if!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 848w, https://substackcdn.com/image/fetch/$s_!K9if!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 1272w, https://substackcdn.com/image/fetch/$s_!K9if!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K9if!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png" width="1400" height="1115" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1115,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!K9if!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 424w, https://substackcdn.com/image/fetch/$s_!K9if!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 848w, https://substackcdn.com/image/fetch/$s_!K9if!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 1272w, https://substackcdn.com/image/fetch/$s_!K9if!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d5a41b7-50db-4633-8f84-4aa306fcea5e_1400x1115.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you&#8217;d like a taste of the full course, <strong><a href="https://vanishing-gradients.kit.com/8fff155b0c">I&#8217;ve put together a free 10-part email series on building LLM-powered apps</a></strong>. It walks through practical strategies for escaping proof-of-concept purgatory: one clear, focused email at a time.</p><p><strong>Copyrights:</strong> The article was originally published in collaboration with Jordan Cutler on High Growth Engineer &#8212; <a href="https://read.highgrowthengineer.com/p/stop-building-ai-agents-heres-what-to-do-instead">original article</a>.</p><p>If not otherwise stated, all images are created by the author.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Here comes another bubble (2025)]]></title><description><![CDATA[The AI hype song]]></description><link>https://www.marvelousmlops.io/p/here-comes-another-bubble-2025</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/here-comes-another-bubble-2025</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Sat, 21 Jun 2025 17:59:03 GMT</pubDate><enclosure url="https://api.substack.com/feed/podcast/166478728/af47fb8ecc5415b77e083a786260c8f8.mp3" length="0" type="audio/mpeg"/><content:encoded><![CDATA[<p>"AI is not a bubble", says everyone who is deeply invested financially or reputationally in keeping the momentum alive.</p><p>Bubbles in technology are not new. In 2007, an a capella group The Richter Scales created a song "Here comes another bubble". They received an Webby Award for Viral Video. Today, this video is more relevant than ever!<br><br>I decided to create a parody on this song. Lyrics is mostly written by me (ChatGPT is not great in understanding the beat). The song is made with AI!<br><br>Lyrics:<br><br>Almost got a CS degree<br>Stanford dropout, dreaming free<br>Watched the hype and took the bait<br>Now I&#8217;m coding AI fate</p><p>November thirtieth, it dropped<br>ChatGPT &#8212; the world has stopped<br>Everyone just paused and stared<br>Google watched &#8212; but wasn't scared</p><p>GPT-4 made its mark<br>Claude appeared, polite and smart<br>Google scrambled, shipped out Bard<br>(Launching LLMs is hard)</p><p>Here comes another bubble<br>AI&#8217;s gone full throttle<br>Scraping every novel</p><p>Couldn&#8217;t sleep, was up all night<br>Turned my scripts into a site<br>Chased the trend &#8212; it never ends<br>Now I'm running CodeWithFriends</p><p>Left my crypto days behind<br>Bragged I&#8217;ve built a thinking mind<br>AI agent, it's so hot<br>With MCP &#8212; I kid you not</p><p>Dreamed I'd close a massive seed<br>"AGI" was all I'd need<br>Even though it&#8217;s held with tape<br>VCs came to seal the fate</p><p>Here comes another bubble<br>The VCs are buzzing<br>AI does everything (but nothing)</p><p>Launched a demo late one night<br>Woke up, X has lost its mind<br>Angels circled, term sheets flew<br>&#8220;Pre-revenue? We love that too&#8221;</p><p>Did demo live, it didn&#8217;t crash<br>Woke up trending, raised more cash<br>Forbes 30 Under 30 call<br>Didn&#8217;t know I&#8217;d peaked at all</p><p>Blog it, film it, make those reels<br>TikTok trends and viral feels<br>Tweet it, thread it, demo day<br>Build a bot that writes your way</p><p>Here comes another bubble<br>In a year, we&#8217;ll pivot<br>AGI &#8212; we live it</p><p>&#8220;Helps you code&#8221; but wrecks your build<br>Kills the flow, your patience spilled<br>System broken, things unclear<br>Keep believing, A-G-I is near</p><p>Billion tokens burned in vain<br>Models hallucinate again<br>Now your startup&#8217;s pivoted<br>To cr&#232;me br&#251;l&#233;e with AI flame</p><p>Here comes another bubble<br>And when we are gone<br>This will still go on<br>And on and on and on<br>And on and on<br>POP!</p>]]></content:encoded></item><item><title><![CDATA[Using Polars in unison with Databricks Unity Catalog]]></title><description><![CDATA[The advent of Polars is not surprising given the performance it delivers.]]></description><link>https://www.marvelousmlops.io/p/using-polars-in-unison-with-databricks</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/using-polars-in-unison-with-databricks</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Sat, 24 May 2025 13:57:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!B-25!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The advent of Polars is not surprising given the performance it delivers. Recently, it became also possible to use external engines for consuming data from Databricks Unity Catalog (UC), which means we can read data directly into a Polars dataframe by utilizing pyarrow and Deltalake.</p><p>This can be particularly useful for machine learning projects, which utilize libraries like scikit-learn that expect Pandas or Polars dataframe as an input. If the source data is stored as a delta table in Unity Catalog, a standard approach of first creating a pyspark dataframe and then transforming it into a Polars or Pandas dataframe can be very inefficient. Instead, we would like to read data from Unity Catalog without going through pyspark.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B-25!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B-25!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!B-25!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!B-25!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!B-25!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B-25!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:152116,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://marvelousmlops.substack.com/i/164304070?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!B-25!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!B-25!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!B-25!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!B-25!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feafbf946-14bb-4290-9b88-1a7c5acefd19_1024x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this article, we show how to achieve that. We also challenge the idea of using pyspark or spark SQL for data transformation (which is common for data preprocessing pipelines), showing that using Polars can help to achieve serious speedup and cost savings for certain data sizes.</p><h2><strong>Benchmark &amp; requirements</strong></h2><p>For the benchmark we used the industry standard TPC-H benchmark: <a href="https://www.tpc.org/tpch/">https://www.tpc.org/tpch/</a>. The functions are the ones as used again from the repository from Polars: <a href="https://github.com/pola-rs/polars-benchmark">https://github.com/pola-rs/polars-benchmark</a>.</p><p>All the code for the article can be found in the<a href="https://github.com/marvelousmlops/polars_uc_benchmark"> repository</a>. For your convenience, we made all the data available via a <a href="https://drive.google.com/drive/folders/1G51lYGnW-XRZ08bU-A3_i67PBoH3UBm4?usp=share_link">shared folder</a> and prepared the scripts to dump the files in a Volume, and create the external tables with the feature of deleteVectors turned off. You can run the notebooks yourself to experience the difference in performance with minimal effort.</p><p><strong>These are the steps:</strong></p><ul><li><p>Clone the repository into your Databricks workspace using the Git<a href="https://docs.databricks.com/aws/en/repos/"> folder feature</a>. It is a public repository, so you do not have to authenticate</p></li></ul><ul><li><p>Update project_config file to use your preferred catalog and schema</p></li><li><p>Toggle &#8216;External Data Access&#8217; via the Metastore Toggle in the Catalog Explorer (Figure 1).</p></li><li><p>Grant the EXTERNAL_USE_SCHEMA privilege to your user. This will be exposed once the toggle above has switched. Note: ALL_PRIVILEGES does not include the EXTERNAL_USE_SCHEMA privilege (Figure 2).</p></li><li><p>Run notebooks/init_benchmark_uc.py script (you need to have permissions to create schemas and volumes) to download the data from the shared folder and create delta tables</p></li><li><p>Execute notebooks/run_notebooks.py using Serveless cluster vs classic compute.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0euc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0euc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 424w, https://substackcdn.com/image/fetch/$s_!0euc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 848w, https://substackcdn.com/image/fetch/$s_!0euc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 1272w, https://substackcdn.com/image/fetch/$s_!0euc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0euc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png" width="1084" height="662" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:662,&quot;width&quot;:1084,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0euc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 424w, https://substackcdn.com/image/fetch/$s_!0euc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 848w, https://substackcdn.com/image/fetch/$s_!0euc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 1272w, https://substackcdn.com/image/fetch/$s_!0euc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52e0f9b3-0ba5-4ddf-8cfb-89d8c315e4a3_1084x662.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1. External data access toggle</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KsK8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KsK8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 424w, https://substackcdn.com/image/fetch/$s_!KsK8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 848w, https://substackcdn.com/image/fetch/$s_!KsK8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 1272w, https://substackcdn.com/image/fetch/$s_!KsK8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KsK8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png" width="1456" height="796" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KsK8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 424w, https://substackcdn.com/image/fetch/$s_!KsK8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 848w, https://substackcdn.com/image/fetch/$s_!KsK8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 1272w, https://substackcdn.com/image/fetch/$s_!KsK8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76b54b3-91e0-4a3d-8ed4-dca99523cf0e_1600x875.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2. EXTERNAL USE SCHEMA privilege</figcaption></figure></div><p>To read data from Unity Catalog, we will use the mechanism of <a href="https://learn.microsoft.com/en-gb/azure/databricks/external-access/credential-vending">credential vending</a>. It works using short-living tokens and entirely bypasses the dependency of Spark. In the notebook, we use a personal access token that we retrieve using dbutils. For production, you need to create a service principal and store its token in a secret scope.</p><h2><strong>Findings</strong></h2><p>As expected, on smaller datasets (x1, x3 scale) Polars runs faster than PySpark (Figure 3). Up to 2.5 times as fast (as measured by rerunning the TPC-H benchmark as defined in the repo for the Polars benchmark). </p><p>The problem is, it doesn&#8217;t scale well (serverless supports max 32 gb RAM). With the demonstrated approach you load all the data directly into the local RAM and execute. There are ways to compute out-of-core, but at the time of writing not possible with reliability: the short-living token connection with Unity Catalog does not seem very reliable. For some reason the load data command keeps hanging without timing out, you really need to reset the entire instance for it to work again. When running the code locally, this issue does not seem to be present.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G7eZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G7eZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 424w, https://substackcdn.com/image/fetch/$s_!G7eZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 848w, https://substackcdn.com/image/fetch/$s_!G7eZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 1272w, https://substackcdn.com/image/fetch/$s_!G7eZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G7eZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png" width="1320" height="1176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1176,&quot;width&quot;:1320,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:215032,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://marvelousmlops.substack.com/i/164304070?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G7eZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 424w, https://substackcdn.com/image/fetch/$s_!G7eZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 848w, https://substackcdn.com/image/fetch/$s_!G7eZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 1272w, https://substackcdn.com/image/fetch/$s_!G7eZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53c94e8-1f22-4303-a570-34a8954e4ee2_1320x1176.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 3. Speed benchmark overview</figcaption></figure></div><p>When it comes to the costs, on x1 scale on serverless (32 gb), using polars is at least 15x cheaper than running the same workload using Spark (total cost is 0,183 dollar vs 3,267 dollar, for 15 runs). Figure 4 shows the distribution of individual runs. On a larger scale (x10), using Spark SQL would be more cost effective.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0LQT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0LQT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 424w, https://substackcdn.com/image/fetch/$s_!0LQT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 848w, https://substackcdn.com/image/fetch/$s_!0LQT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 1272w, https://substackcdn.com/image/fetch/$s_!0LQT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0LQT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0LQT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 424w, https://substackcdn.com/image/fetch/$s_!0LQT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 848w, https://substackcdn.com/image/fetch/$s_!0LQT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 1272w, https://substackcdn.com/image/fetch/$s_!0LQT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1df49fc-cbb3-4175-8e00-9ec677288480_1520x852.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 4. Costs box plot </figcaption></figure></div><h4><strong>Downsides</strong></h4><ol><li><p><strong>Reading from Unity Catalog. </strong>Currently (20-05-2025) Polars doesn&#8217;t seem to support it to read directly from Unity Catalog: an obscure error is raised that mentions the URL, despite trying various methods. However, what did work is to read the table via the library <a href="https://pypi.org/project/deltalake/">deltalake</a>. This can be used to expose an arrow dataset, which subsequently can be loaded into Polars. Probably in the near future direct read can be supported.</p></li><li><p><strong>Writing to Unity Catalog. </strong>This seems not yet possible as both the Deltalake and Polars libraries raise an error when trying. However, it can be done by writing directly to the storage as delta table, and creating an external table referring to that data.</p></li><li><p><strong>Breaking the lineage. </strong>Databricks' Unity Catalog captures post-facto lineage at runtime. You need to have executed code, for it to be able to pick up what the lineage between data objects was. However, this only works for pyspark. Databricks is working on the Bring Your Own Lineage feature, which will allow users to update the lineage themselves. This would solve, with effort, the downside of running an external engine: losing the lineage. For machine learning projects, we could tag MLflow experiment runs, passing information about the table location, and the version of delta table.</p></li></ol><h2><strong>Conclusions</strong></h2><p>Reading data directly from a delta table without using Spark is a great idea if you do not need Spark for your workload (for example, for a machine learning workflow). It is possible to run the code anywhere: on your local PC, standard cluster, or serverless.</p><p>For data preprocessing, it may be beneficial to use polars too (for smaller datasets). You can save quite some costs &#8211; at a cost. The cost being that you lose the sweet features that the Spark on Databricks provides; mostly the lineage, but also that not all features of the tables are supported and need to be turned off. What you save can be quite impressive, though: running a (small) load on Serverless, you can in the smallest instance reach a speed up of 3x on Serverless in comparison with PySpark, or a 10x speedup when running locally on a Macbook M4 Pro.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How to debug ML deployments 20x faster]]></title><description><![CDATA[Test Databricks model deployments locally]]></description><link>https://www.marvelousmlops.io/p/how-to-debug-ml-deployments-20x-faster</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/how-to-debug-ml-deployments-20x-faster</guid><dc:creator><![CDATA[Mehmet Acikgoz]]></dc:creator><pubDate>Thu, 01 May 2025 16:56:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!URHL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Real-time model serving is an aspect of MLOps that most machine learning teams still struggle with. Often, the deployment part is outsourced to the DevOps team, and the machine learning team is responsible for the model training and handing over the model artifact. This split of responsibilities (especially if teams have different targets) is not ideal: changes in the model training code would mean that the deployment part also needs to be adjusted, which requires a lot of coordination between the teams.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!URHL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!URHL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 424w, https://substackcdn.com/image/fetch/$s_!URHL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 848w, https://substackcdn.com/image/fetch/$s_!URHL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 1272w, https://substackcdn.com/image/fetch/$s_!URHL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!URHL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png" width="540" height="540" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:540,&quot;width&quot;:540,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!URHL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 424w, https://substackcdn.com/image/fetch/$s_!URHL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 848w, https://substackcdn.com/image/fetch/$s_!URHL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 1272w, https://substackcdn.com/image/fetch/$s_!URHL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6879d3d1-1466-4cb2-b494-61f8b244bcc5_540x540.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We believe that the machine learning teams should be responsible for machine learning model deployment end-to-end. However, they often lack the skills, especially when it comes to the real-time model serving. Luckily, there are tools that aim to simplify that part of the deployment, if you are a Databricks user. Databricks model serving is worth considering for that purpose: models can be deployed with minimal code using Python SDK, given that the model training is tracked using MLflow and registered in Unity Catalog.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>It all sounds simple, but has a downside: if something is wrong with your deployment (especially if you are using a custom model), you will only see it after you have waited for 15-20 minutes. Very time-consuming (and expensive) interaction cycle&#8230; But it does not have to be that way! Databricks model serving <a href="https://mlflow.org/docs/latest/deployment/index.html">utilizes MLflow model serving</a>, which means, you can also test it locally.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yeiS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yeiS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 424w, https://substackcdn.com/image/fetch/$s_!yeiS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 848w, https://substackcdn.com/image/fetch/$s_!yeiS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 1272w, https://substackcdn.com/image/fetch/$s_!yeiS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yeiS!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png" width="1200" height="612.3626373626373" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:743,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!yeiS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 424w, https://substackcdn.com/image/fetch/$s_!yeiS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 848w, https://substackcdn.com/image/fetch/$s_!yeiS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 1272w, https://substackcdn.com/image/fetch/$s_!yeiS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee99c0a-1fb0-4e42-8e78-61d796dd7b20_1600x817.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Source: official MLflow documentation, <a href="https://mlflow.org/docs/latest/deployment/index.html">https://mlflow.org/docs/latest/deployment/index.html</a></figcaption></figure></div><p>In this article, we&#8217;ll demonstrate the local testing workflow using the well-known Iris-Species dataset, focusing on three critical steps:</p><ol><li><p>Download a pyfunc model artifact from MLflow experiment tracking.</p></li><li><p>Deploy the model endpoint locally.</p></li><li><p>Test the endpoint.</p></li></ol><p>Before we proceed with these steps, let&#8217;s walk through the prerequisites and code we used to train the Iris model and log it in MLflow.</p><h2>Prerequisites</h2><p>First of all, make sure you have Databricks CLI installed. Follow the<a href="https://www.google.com/search?client=safari&amp;rls=en&amp;q=databricks+cli+install&amp;ie=UTF-8&amp;oe=UTF-8"> instructions</a> if it is not the case. Authenticate to your Databricks workspace. As a result, you will have a .databrickscfg file with a profile.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O7qy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O7qy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 424w, https://substackcdn.com/image/fetch/$s_!O7qy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 848w, https://substackcdn.com/image/fetch/$s_!O7qy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 1272w, https://substackcdn.com/image/fetch/$s_!O7qy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O7qy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png" width="932" height="276" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b99276da-0db1-4e50-b231-30026d5fffac_932x276.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:276,&quot;width&quot;:932,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!O7qy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 424w, https://substackcdn.com/image/fetch/$s_!O7qy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 848w, https://substackcdn.com/image/fetch/$s_!O7qy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 1272w, https://substackcdn.com/image/fetch/$s_!O7qy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb99276da-0db1-4e50-b231-30026d5fffac_932x276.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><blockquote><p>The code for this article can be found in this repository: <a href="https://github.com/marvelousmlops/iris-mlflow-serve-testing">https://github.com/marvelousmlops/iris-mlflow-serve-testing</a>.</p></blockquote></blockquote><p>Clone the repository, and set up your virtual environment with all the dependencies by running the following command:</p><pre><code><code>uv sync &#8211;-all-extras</code></code></pre><h2>Model training &amp; MLflow experiment tracking</h2><p>First of all, we get the data and train the sklearn pipeline to predict the Iris species.</p><pre><code><code>from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split


iris = datasets.load_iris(as_frame=True)
X = iris.data
y = iris.target
logger.info("The dataset is loaded.")


X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=28)
preprocessor = ColumnTransformer(
               transformers=[("std_scaler", StandardScaler(), iris.feature_names)])


pipeline = Pipeline(steps=[("preprocessor", preprocessor), 
                          ("classifier", LogisticRegression()),
                          ])
pipeline.fit(X_train, y_train)</code></code></pre><p>After that, we define a custom model wrapper:</p><pre><code><code>import pandas as pd
import numpy as np
from typing import Union
from mlflow.pyfunc import PythonModelContext


class ModelWrapper(mlflow.pyfunc.PythonModel):
   def __init__(self, model: object) -&gt; None:
       self.model = model
       self.class_names = iris.target_names


   def predict(
       self,
       context: PythonModelContext,
       model_input: Union[pd.DataFrame, np.array], 
   ) -&gt; Union[pd.DataFrame, np.ndarray]: 
       raw_predictions = self.model.predict(model_input)
       mapped_predictions = [self.class_names[int(pred)] for pred in raw_predictions]
       return mapped_predictions</code></code></pre><p>This model wrapper is pretty basic: the only thing we change is that the pipeline outputs the name of the iris species instead of a number. Of course, you can put any logic in such a wrapper class and make it more complex.</p><p>Finally, we log the model together with some metrics and parameters. It is important to specify the profile in the MLflow tracking URI (it is the same as the profile name in the .databrickscfg file).</p><pre><code><code>import mlflow
from loguru import logger
from mlflow.models import infer_signature

mlflow.autolog(disable=True)
# mlflow.set_tracking_uri(&#8220;databricks://your-profile&#8221;) - use it to run locally
mlflow.set_tracking_uri("databricks")
mlflow.set_experiment("/Shared/iris-demo")


with mlflow.start_run() as run:
   run_id = run.info.run_id
   y_proba = pipeline.predict_proba(X_test)


   # Evaluation metrics
   auc_test = roc_auc_score(y_test, y_proba, multi_class='ovr')
   logger.info(f"AUC Report: {auc_test}")


   # Log parameters and metrics
   mlflow.log_param("model_type", "LogisticRegression Classifier with preprocessing")
   mlflow.log_metric("auc", auc_test)


   # Log the model
   signature = infer_signature(model_input=X_train, model_output=["setosa"])
   dataset = mlflow.data.from_pandas(iris.frame, name="train_set")
   mlflow.log_input(dataset, context="training")


   mlflow.pyfunc.log_model(
       python_model=ModelWrapper(pipeline),
       artifact_path=f"pyfunc-lg-pipeline-model",
       signature=signature,
   )
</code></code></pre><h2>Downloading the artifacts</h2><p>This is how the logged model looks like in the Databricks workspace:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XKKD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XKKD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 424w, https://substackcdn.com/image/fetch/$s_!XKKD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 848w, https://substackcdn.com/image/fetch/$s_!XKKD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 1272w, https://substackcdn.com/image/fetch/$s_!XKKD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XKKD!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png" width="1200" height="738.4615384615385" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:896,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!XKKD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 424w, https://substackcdn.com/image/fetch/$s_!XKKD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 848w, https://substackcdn.com/image/fetch/$s_!XKKD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 1272w, https://substackcdn.com/image/fetch/$s_!XKKD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b8dcaba-d50d-4782-a03c-fcab30fe814d_1600x985.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To download an artifact from your Databricks workspace to your local machine, you first need to configure your environment for secure access.</p><p>Start by setting the MLflow tracking URI to point to your Databricks workspace:</p><pre><code><code>export MLFLOW_TRACKING_URI=databricks://&lt;your profile name&gt;</code></code></pre><p>Then, authenticate using Databricks CLI:</p><pre><code><code>databricks auth login --profile &lt;your profile name&gt;</code></code></pre><p>Once authenticated, you can use the MLflow CLI to download artifacts. The following command downloads a specific artifact from a given MLflow run to a designated local directory:</p><pre><code><code>mlflow artifacts download -r &lt;mlflow_run_id&gt; -a &lt;artifact_name&gt; -d &lt;destination&gt;</code></code></pre><p>For example, to download a model artifact named <code>`pyfunc-lg-pipeline-model`</code>from a particular run, you might use:</p><pre><code><code>uv run mlflow artifacts download -r b4bad079af4649dba6fffc51f33b8902 -a pyfunc-lg-pipeline-model -d .</code></code></pre><p>This flow ensures that ML artifacts tracked in Databricks are easily retrievable for local testing, validation, or deployment pipelines, supporting robust ML operations and reliable API contract testing. This generates a local directory with the following structure inside <strong>iris-mlflow-serve-testing</strong> project:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cyYp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cyYp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 424w, https://substackcdn.com/image/fetch/$s_!cyYp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 848w, https://substackcdn.com/image/fetch/$s_!cyYp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 1272w, https://substackcdn.com/image/fetch/$s_!cyYp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cyYp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png" width="514" height="488" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:488,&quot;width&quot;:514,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!cyYp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 424w, https://substackcdn.com/image/fetch/$s_!cyYp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 848w, https://substackcdn.com/image/fetch/$s_!cyYp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 1272w, https://substackcdn.com/image/fetch/$s_!cyYp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d4ae12b-313d-46fc-8afc-436dae2e6feb_514x488.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The <code>`MLmodel`</code> file defines the model&#8217;s flavors (e.g., <code>`python_function`</code> or <code>`sklearn`</code>), while `conda.yaml` and `requirements.txt` ensure environment reproducibility.</p><h2>Local Model Deployment</h2><p>At this stage, you have already set up a virtual environment as described in the prerequisites step. Make sure that pyenv is installed before you proceed.</p><p>Now you can serve the model through MLflow's local inference server on a custom port (5088 in this example):</p><pre><code><code>uv run mlflow models serve -m pyfunc-lg-pipeline-model --port 5088</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7q_L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7q_L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 424w, https://substackcdn.com/image/fetch/$s_!7q_L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 848w, https://substackcdn.com/image/fetch/$s_!7q_L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 1272w, https://substackcdn.com/image/fetch/$s_!7q_L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7q_L!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png" width="1200" height="347.6944253269098" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:421,&quot;width&quot;:1453,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!7q_L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 424w, https://substackcdn.com/image/fetch/$s_!7q_L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 848w, https://substackcdn.com/image/fetch/$s_!7q_L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 1272w, https://substackcdn.com/image/fetch/$s_!7q_L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b898d2-d5f6-430f-9295-05da5150c5eb_1453x421.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now we are ready to test the model locally. The MLflow model server will listen for incoming requests on the specified port (5088). You can send requests to this endpoint to test the model's behavior.</p><h2>Endpoint testing</h2><p>At this early stage, testing the Inference API Contract &#8212; checking input/output formats, response consistency &#8212; helps ensure your model will work smoothly with downstream systems. Doing this early means you catch integration problems before scaling up to full deployment on Databricks, saving time and reducing headaches later.</p><p><a href="https://mlflow.org/docs/latest/deployment/deploy-model-locally#endpoints">MLflow Inference Server</a> provides 4 endpoints: ping, health, version and invocations. As Databricks model serving differs from the local deployment, and we can only test to a certain extent, we are only interested in the <em>/invocations</em> endpoint that returns model&#8217;s predictions for a given input.</p><p>The <em>/invocations</em> endpoint accepts data in the dataframe_split of dataframe_records format (it is also possible to provide Tensor input, but that would require a different model specification, hence is not relevant for our example).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Uhqa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Uhqa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 424w, https://substackcdn.com/image/fetch/$s_!Uhqa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 848w, https://substackcdn.com/image/fetch/$s_!Uhqa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 1272w, https://substackcdn.com/image/fetch/$s_!Uhqa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Uhqa!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png" width="1200" height="517.5824175824176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:628,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Uhqa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 424w, https://substackcdn.com/image/fetch/$s_!Uhqa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 848w, https://substackcdn.com/image/fetch/$s_!Uhqa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 1272w, https://substackcdn.com/image/fetch/$s_!Uhqa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a840b9-eaaa-4ea8-8026-e2b810b882c1_1460x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As mentioned in <a href="https://mlflow.org/docs/latest/deployment/deploy-model-locally#endpoints">the documentation</a>, it is not advised to use <code>dataframe_records</code>, let&#8217;s proceed with testing using <code>dataframe_split</code> instead.</p><p>Assume we have the following <code>test_data</code> and we need to convert them to a pandas dataframe so that we can pass the data to the endpoint.</p><pre><code><code>BASE_URL = &#8220;http://127.0.1.1:5088&#8221;

test_data = {
    "sepal length (cm)": 6.1,
    "sepal width (cm)": 2.8,
    "petal length (cm)": 4.7,
    "petal width (cm)": 1.2,
}

pandas_df = pd.DataFrame([test_data])

payload_dataframe_split = json.dumps(
    {"dataframe_split": pandas_df.to_dict(orient="split")}
)
</code></code></pre><p>Let&#8217;s do the following tests:</p><ul><li><p>When the correct input (a single observation or a dataframe with multiple observations) is provided, we get status 200</p></li><li><p>When we provide input with too few fields, we get status 400</p></li></ul><p>Here is an example of one such test:</p><pre><code><code>def test_inference_server_invocations() -&gt; None:
   """Test model invocations using split dataframe format.
   Verifies successful response and valid prediction format."""

   response = requests.post(
       f"{BASE_URL}/invocations",
       data=payload_dataframe_split,
       headers={"Content-Type": "application/json"},
       timeout=2,
   )
   assert response.status_code == 200
   logger.info(f"Received {response.json()}")
   value = response.json()["predictions"]
   assert isinstance(value, list)</code></code></pre><p>We can simply run <code>`uv run pytest`</code> and make sure all tests pass.</p><h2>Conclusions</h2><p>Deploying machine learning models can be tricky, especially when different teams handle model creation and deployment. It's easy to run into issues that take time to fix once the model is live.</p><p>Testing your models locally before deploying them to Databricks can save you time and headaches. By downloading your model, running it on your computer, and testing its inputs and outputs, you can catch problems early. This way, you can be more confident that your model will work correctly when deployed, leading to a smoother process overall. Tools like MLflow make this local testing easier. This approach means fewer surprises and a more reliable deployment.</p><blockquote><blockquote><p><strong>To learn more about MLOps with Databricks</strong>, join our <strong><a href="https://maven.com/marvelousmlops/mlops-with-databricks?promoCode=MARVELOUS">End-to-end MLOps with Databricks course</a></strong>. <br><br><strong>It covers everything you need for an actual production-grade AI application</strong>, from developing on Databricks, experiment tracking with MLflow, registering models in Unity Catalog, deploying real-time endpoints, workflow orchestration, and CI/CD pipelines.</p></blockquote><blockquote><p><strong>&#128176; Use code MARVELOUS to get 100 EUR off.</strong></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K0yv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K0yv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 424w, https://substackcdn.com/image/fetch/$s_!K0yv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 848w, https://substackcdn.com/image/fetch/$s_!K0yv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 1272w, https://substackcdn.com/image/fetch/$s_!K0yv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K0yv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png" width="1456" height="1095" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1095,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:295727,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://decodingml.substack.com/i/161954652?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!K0yv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 424w, https://substackcdn.com/image/fetch/$s_!K0yv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 848w, https://substackcdn.com/image/fetch/$s_!K0yv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 1272w, https://substackcdn.com/image/fetch/$s_!K0yv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be77e3b-3166-48f4-bda1-1c69e4e818eb_1516x1140.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Join the <strong><a href="https://maven.com/marvelousmlops/mlops-with-databricks?promoCode=MARVELOUS">End-to-end MLOps with Databricks</a></strong> course - Use code MARVELOUS to get <strong>100 EUR off.</strong></figcaption></figure></div></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Unicorns and Rainbows: The Reality of Implementing AI in a Corporate]]></title><description><![CDATA[The AI Bubble: Hype, herd mentality, and harsh realities]]></description><link>https://www.marvelousmlops.io/p/unicorn-and-rainbows-the-reality</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/unicorn-and-rainbows-the-reality</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Wed, 05 Feb 2025 13:25:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!STxH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Unicorns and Rainbows. </strong>Is it a metaphor? Is it a reality? Maybe both. Think of an unicorn dancing on top of a radiant rainbow. But, in fact, what does it mean?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!STxH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!STxH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 424w, https://substackcdn.com/image/fetch/$s_!STxH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 848w, https://substackcdn.com/image/fetch/$s_!STxH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!STxH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!STxH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg" width="623" height="620" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:620,&quot;width&quot;:623,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!STxH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 424w, https://substackcdn.com/image/fetch/$s_!STxH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 848w, https://substackcdn.com/image/fetch/$s_!STxH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!STxH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6510788-9239-453c-bdd8-d531dc5f364e_623x620.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image generated by AI</figcaption></figure></div><p>Humanity has always been drawn to <strong>utopia</strong>&#8202;&#8212;&#8202;a perfect, idealized future where all problems are solved. Believing that the world is steadily marching toward this vision is tempting. In the AI landscape, the unicorn (you have noticed the <strong>5th leg</strong>, right?) represents the elevated promises, wild imagination, and relentless hype that paint a picture of transformative, almost magical technology.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>The rainbow, however, represents the real world: entire potential but riddled with imperfections, inconsistencies, and systemic barriers.</p><p>Just like the stock market, AI has its declines and flows. Everything might seem to skyrocket, but a slight shift&#8202;&#8212;&#8202;technical debt, regulatory burdens, or enterprise realities&#8202;&#8212;&#8202;can send it crashing back to earth. The question is not whether AI is a transformative force (there is no doubt it is!) but whether we&#8217;re being realistic about its trajectory.</p><p>This article will discuss the reality of using AI in the enterprise environment, address technical debt, bridge knowledge gaps, and understand the herd effect that fuels the AI bubble. We aim to offer a realistic roadmap for businesses navigating the complex AI landscape by critically analyzing these factors.</p><h3><strong>1. The AI bubble</strong></h3><p>We have been in Data &amp; AI for over 10 years. The AI bubble has never been so big. We have AI everywhere on our laptops, phones, and websites. The CEOs of Nvidia, Microsoft, Meta, and OpenAI are spreading a lot of news about revolutionary AI technology, how AI agents will replace humans, how we will reach AGI soon, and how we will have AI everywhere. We live in an AI bubble, and even though the technology is accurate, it is nontrivial to apply it to actual use cases and drive business value than advertised.</p><p>The technological advancements in the AI field are significant, and the value AI generates is real. However, there are still many gaps that people who try to build AI products see clearly. Two factors contribute to the AI bubble: knowledge and the herd effect. Somehow, they are tangential but different.</p><h3><strong>2. The Role of Knowledge Gaps</strong></h3><p>The gap between AI insiders and the general public is one of the hinds of the AI bubble. The saying &#8220;Knowledge is power&#8221; remains valid for AI within its development and implementation context.</p><p>People who are deeply invested in the development of AI are fully aware of the nuances, challenges, and limitations that come with the implementation of AI-based solutions.</p><p>On the other hand, AI outsiders are constantly awe-struck by the marketing terms associated with AI which presents an entirely different world of possibilities. This knowledge gap enables misconceptions to spread at an alarming rate, therefore making the hype of AI take precedence over the reality of what AI systems can offer.</p><h3><strong>3. Herd Effect: Fear of Missing Out (FOMO)</strong></h3><p>Another significant factor driving the AI hype is the herd effect or the Fear of Missing Out (FOMO).</p><p>As more companies invest in AI and tout their successes, others feel compelled to follow suit, fearing they&#8217;ll fall behind if they don&#8217;t adopt AI technologies. This rush often leads to deploying AI solutions without a thorough understanding of their applicability or potential ROI, further inflating the AI bubble. The result is a market saturated with AI buzzwords and solutions that may not deliver the promised transformative impact.</p><p>AI models (under AI models, we understand foundation models) are used everywhere, where a standard ML model should be used instead. This adds complexity and decreases reliability.</p><h3><strong>4. Back to basics</strong></h3><p>Most companies can not reliably bring standard machine learning models to production and lack monitoring practices.</p><p>Despite what many people think, workflows that include AI models are, on average, more complex to bring into production and monitor&#8202;&#8212;&#8202;even if you take the simplest scenario without RAG or finetuning involved&#8202;&#8212;&#8202;just call a 3rd party API.</p><p>In too many cases, we seem to have forgotten the basic principles of machine learning and blindly rely on what that API outputs. This is the danger of AI hype: AI has become accessible to everyone, and many software engineers treat it as just another API call.</p><p>What could go wrong? The data model has not changed, the code has not changed (and neither has the environment where it gets executed), and the version of the API has not changed. But this is the beauty of machine learning: even if everything in your control has not changed, the model can start performing poorly unexpectedly because data distribution has changed.</p><p>This does not just happen with standard machine learning models, it also happens with AI models&#8202;&#8212;&#8202;we just have less means to impact that behavior, and prompt finetuning becomes an essential part of the process.</p><h3>5. <strong>Real-world, 2025</strong></h3><p>Experts say that 2025 will be the year of AI agents. <strong>But is it really true?</strong></p><p>While the AI hype machine continues to boom, real-world adoption tells a different story. The promise of autonomous AI agents seamlessly operating across enterprises remains largely aspirational. The reality? AI in enterprise is still a work in progress&#8202;&#8212;&#8202;complex, expensive, and often misaligned with actual business needs.</p><p>Take <strong>BBVA</strong>, the Spanish bank that <a href="https://www.wsj.com/articles/six-months-thousands-of-gpts-and-some-big-unknowns-inside-openais-deal-with-bbva-5d6f1c03?utm_source=chatgpt.com">went all in</a> on OpenAI&#8217;s technology. They deployed over 2,900 AI models to enhance productivity, yet integrating them into their existing systems turned out to be a logistical nightmare. AI doesn&#8217;t operate in a vacuum; it needs to connect with legacy infrastructure, existing workflows, and strict regulatory requirements. And that&#8217;s where reality bites&#8202;&#8212;&#8202;scaling AI across an enterprise is exponentially harder than rolling out a chatbot.</p><p>The UK government&#8217;s attempt to integrate AI into its welfare system <a href="https://www.theguardian.com/technology/2025/jan/27/ai-prototypes-uk-welfare-system-dropped?utm_source=chatgpt.com">faced significant limitations</a>. At least six AI prototypes, designed to enhance staff training, improve job center services, and streamline disability benefit processing, were discontinued due to issues in scalability, reliability, and insufficient testing. Officials acknowledged several &#8220;frustrations and false starts,&#8221; highlighting the complexities involved in deploying AI within public services.</p><p><a href="https://www.architectureandgovernance.com/artificial-intelligence/new-research-uncovers-top-challenges-in-enterprise-ai-agent-adoption/?utm_source=chatgpt.com">A study </a>highlighted several obstacles in developing and deploying AI agents within enterprises. Security concerns were identified as a top challenge by leadership (53%) and practitioners (62%). Other significant challenges included data governance, performance issues, and integration complexity. These findings underscore the multifaceted difficulties organizations face in implementing AI agents effectively.</p><p>Reflecting on these examples, it&#8217;s evident that the widespread adoption of AI agents in enterprise settings faces significant limitations. While 2025 may usher in extensive research, proofs of concept (POCs), and minimum viable products (MVPs), the path to full-scale integration remains complex.</p><h3>6. AI in a corporate environment</h3><p>Big companies operate under strict rules, structured workflows, and a constant focus on ROI. Unlike agile startups that can adapt on the fly, large organizations have to deal with complex approval processes, compliance checks, and risk management. All this makes adopting AI a slower process, and the idea of rapid transformation often feels more like a distant dream than something achievable.</p><p>Chip Huyen references the most common LLM applications in her AI engineering book. Enterprises are risk-averse and prefer to deploy internal-facing applications first. From what we have seen so far, even though there is initial support from the leadership to deploy such applications, not enough funding goes to those projects (and unlikely will) as they do not generate direct business value. We are not saying there is no value&#8202;&#8212;&#8202;there is, but it is challenging to convince stakeholders.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yGTc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yGTc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yGTc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yGTc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yGTc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yGTc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg" width="1456" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yGTc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yGTc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yGTc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yGTc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77c9b386-b9dd-4278-85d3-c3f29f739f74_1536x551.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image reinterpreted from <strong>Huyen, C.</strong> (2025). <em>AI Engineering: Building Applications with Foundation Models</em>. <a href="https://www.amazon.com/AI-Engineering-Building-Applications-Foundation/dp/1098166302">Available on Amazon</a></figcaption></figure></div><p>In enterprises, the most common use cases with direct business generation are related to customer service (forwarding customers to the right agents/ processes) and reviewing contracts. These use cases have been there for a while, and have historically been NLP-heavy, and AI models helped to improve these projects.</p><p>Some companies have tried to use LLMs for recommendations and chatbots, and the world has seen enough failures. Here are some examples:</p><p><strong>DPD&#8217;s customer-facing</strong> chatbot, &#8220;Ruby,&#8221; was designed to assist customers with their inquiries. However, due to insufficient safeguards, a user was able to provoke the bot into swearing and composing a poem criticizing the company itself. <a href="https://www.cxtoday.com/conversational-ai/dpds-genai-chatbot-swears-and-writes-a-poem-about-how-awful-it-is/">This incident</a> underscores the importance of implementing strict content moderation protocols and regularly updating AI systems to prevent such occurrences.</p><p>Similarly, Pak&#8217;nSave&#8217;s AI meal planner app, intended to provide innovative recipe suggestions, <a href="https://www.theguardian.com/world/2023/aug/10/pak-n-save-savey-meal-bot-ai-app-malfunction-recipes?utm_source=chatgpt.com">malfunctioned</a> and recommended a combination of ingredients that would produce chlorine gas, labeling it as an &#8220;aromatic water mix.&#8221; This highlights the critical need for rigorous testing and validation of AI outputs, especially in applications directly impacting consumer health and safety.</p><p>It feels like not everyone has learned from it, and we regularly see companies launching AI applications without clear business value with poor guardrails, mainly for &#8220;marketing purposes&#8221;. Let&#8217;s hope it will not turn out to be bad marketing, as users will try to make the app do things it is not supposed to do &#8220;just for fun&#8221;.</p><p>There are exceptions. Some companies created nice LLM-powered recommendations, for example, Zalando. It has well-implemented guardrails and is useful for the customers (it helps to find items that are otherwise hard to find via search). In October 2024, <a href="https://corporate.zalando.com/en/newsroom/en/stories/zalando-expands-ai-powered-shopping-assistant-25-markets">Zalando expanded its AI-powered assistant to all 25 markets</a>, supporting local languages. This expansion aims to provide customers with personalized fashion advice and insights into emerging local trends, thereby enhancing the shopping experience.</p><h3>7. Areas of attention &amp; conclusions</h3><p>There is great potential to leverage AI in a corporate setting. However, to hope for enterprise adoption, we must consider <strong>security gaps, controlled environments, transparency and traceability</strong>, and a way to monitor and evaluate AI systems.</p><p>In enterprise ecosystems, AI systems need large volumes of data, including personal and proprietary information. Their role is to enhance workflows and boost efficiency, but they need access to critical systems, which can be considered a security risk. Organizations must focus on preventing unapproved access to data, breaches, and compliance issues.</p><p>Threat actors can deploy malware that mimics AI behavior to breach networks, skew decisions, or steal secrets. AI agents act autonomously, making them harder to detect and control. This creates a major challenge: real-time oversight of AI systems.</p><p>Monitoring is a persistent issue. Few companies have proper systems in place, and AI&#8217;s complexity makes it even harder. Owners must fully understand every decision their AI makes</p><p>AI&#8217;s transformative potential is undeniable, but the path from hype to reality is complex and challenging. Rather than chasing unicorns and rainbows, organizations must take a grounded, strategic approach&#8202;&#8212;&#8202;one that prioritizes real business needs, robust security frameworks, and a deep understanding of AI&#8217;s limitations. The road ahead is uncertain, but one thing is clear: the way we answer these questions will determine whether AI becomes a lasting force for good or just another passing bubble.</p><p><strong>What do you think&#8202;&#8212;&#8202;are we ready?</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Navigating Databricks developer tools]]></title><description><![CDATA[Developing on Databricks outside of Databricks environment is challenging, and there are 4 main developer tools Databricks provides:]]></description><link>https://www.marvelousmlops.io/p/navigating-databricks-developer-tools</link><guid isPermaLink="false">https://www.marvelousmlops.io/p/navigating-databricks-developer-tools</guid><dc:creator><![CDATA[Maria Vechtomova]]></dc:creator><pubDate>Sat, 01 Feb 2025 14:00:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TKrg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TKrg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TKrg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!TKrg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!TKrg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!TKrg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TKrg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TKrg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!TKrg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!TKrg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!TKrg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F827c00a7-0f91-4cfe-9882-9fdf8172d619_1600x1200.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Developing on Databricks outside of Databricks environment is challenging, and there are 4 main developer tools Databricks provides:</p><ul><li><p><strong>Databricks CLI:</strong> a command line interface that allows you to interact with Databricks platform. It is a very powerful tool with a large range of commands (essentially, all the functionality available via API or Terraform is also available via CLI).</p></li><li><p><strong>Databricks asset bundles: </strong>developers tools that allow you to simplify deployment of various assets on Databricks. Databricks bundle commands are part of the CLI. Check out a <a href="https://medium.com/marvelous-mlops/getting-started-with-databricks-asset-bundles-5462b366afd0">related article</a>.</p></li><li><p><strong>Databricks Connect&#8202;</strong>&#8212;&#8202;a Python package (there is also support for Scala and R) that allows you to trigger an execution of spark code on a Databricks cluster from a local environment</p></li><li><p><strong>Databricks VS Code Extension</strong>&#8202;&#8212;&#8202;an extension that integrates with all 3 other tools: Databricks connect, Databricks asset bundles, and Databricks connect. Makes it very easy to connect to the Databricks workspace and execute your code using a Databricks cluster.</p></li></ul><p>In this article, we focus on Databricks CLI, Databricks Connect, and VS Code Extension. We will not go through all the features of all these tools but will share some (not very obvious) findings that hopefully will help you in your development and debugging process.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Databricks CLI</h3><p>1.<strong>Installing the CLI. </strong>Databricks has very good documentation on how to install it: <a href="https://docs.databricks.com/en/dev-tools/cli/install.html">https://docs.databricks.com/en/dev-tools/cli/install.html</a></p><p>From our experience, the homebrew option works great on MacOS. On Window&#8202;&#8212;&#8202;winget. Otherwise, you can always install from a source build.</p><p>2.<strong> Authentication. </strong>Databricks CLI should be used to authenticate towards Databricks from your local machine.</p><p>We do not recommend using personal access tokens (from a security perspective, this is not the best option). Instead, use <a href="https://docs.databricks.com/en/dev-tools/cli/authentication.html#oauth-user-to-machine-u2m-authentication">user-to-machine authentication</a>.</p><p>For the workspace-level commands, use the following command to authenticate:</p><pre><code>databricks auth login --host &lt;workspace-url&gt;</code></pre><p>It creates an authentication profile in .databrickscfg file that looks like this (this is not a real host, just an example):</p><pre><code>[dbc-1234a567-b8c9] host      = https://dbc-1234a567-b8c9.cloud.databricks.com/ auth_type = databricks-cli</code></pre><p>One thing to pay attention to is the evaluation order:</p><ol><li><p>For any command run from the bundle working directory&#8202;&#8212;&#8202;the values of fields within a project&#8217;s bundle setting files.</p></li><li><p>The values of environment variables (DATABRICKS_HOST and DATABRICKS_TOKEN)</p></li><li><p>Configuration profile field values within the <code>.databrickscfg</code> file.</p></li></ol><p>If you happen to have an environment variable set up, it might mess up with authentication.</p><h3>Databricks Connect</h3><p>Let&#8217;s try out Databricks Connect now without using VS Code Extension.</p><p>1.<strong>Create &amp; start a cluster.</strong> We use the smallest Personal compute cluster with 15.4 LTS runtime:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NIJV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NIJV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 424w, https://substackcdn.com/image/fetch/$s_!NIJV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 848w, https://substackcdn.com/image/fetch/$s_!NIJV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 1272w, https://substackcdn.com/image/fetch/$s_!NIJV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NIJV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png" width="982" height="876" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:876,&quot;width&quot;:982,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NIJV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 424w, https://substackcdn.com/image/fetch/$s_!NIJV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 848w, https://substackcdn.com/image/fetch/$s_!NIJV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 1272w, https://substackcdn.com/image/fetch/$s_!NIJV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6529bb8-5fee-48be-b1d2-ef00fa985841_982x876.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>2.<strong> Create a demo folder. Create a virtual environment and install databricks-connect package. </strong>We must use Python 3.11.x and databricks-connect version 15.4.x&#8202;&#8212;&#8202;to match the runtime.</p><pre><code>mkdir demo cd demo  uv venv -p 3.11 .venv  source .venv/bin/activate  uv pip install databricks-connect==15.4.4  python</code></pre><p>3.<strong> Run the code. </strong>Now we got into the Python console from the terminal. Let&#8217;s try to run some commands:</p><pre><code>from databricks.connect import DatabricksSession  spark = DatabricksSession.builder.profile("dbc-1234a567-b8c9").getOrCreate()  df = spark.read.table("samples.nyctaxi.trips")  df.show(5)</code></pre><p>A couple of things to note here:</p><ol><li><p>You need to choose the correct profile from the <code>.databrickscfg</code> file. If the profile is named DEFAULT, it is not needed.</p></li><li><p>DatabricksSession will be used to run spark code. If you, for example, run df.toPandas()&#8202;&#8212;&#8202;which may be needed for an ML library you are using, the dataframe will be loaded into your machine&#8217;s memory. And if the data is too big, you will get out of memory errors.</p></li><li><p>It may surprise you, but you can also import pyspark&#8202;&#8212;&#8202;even though pyspark is not listed as a dependency of databricks-connect, and uv does not show that pyspark is installed.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xuKV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xuKV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 424w, https://substackcdn.com/image/fetch/$s_!xuKV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 848w, https://substackcdn.com/image/fetch/$s_!xuKV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 1272w, https://substackcdn.com/image/fetch/$s_!xuKV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xuKV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png" width="1138" height="846" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/707eb336-4747-4721-aa91-d522faf167d0_1138x846.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:846,&quot;width&quot;:1138,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xuKV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 424w, https://substackcdn.com/image/fetch/$s_!xuKV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 848w, https://substackcdn.com/image/fetch/$s_!xuKV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 1272w, https://substackcdn.com/image/fetch/$s_!xuKV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F707eb336-4747-4721-aa91-d522faf167d0_1138x846.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It is because pyspark is &#8220;hidden&#8221; and embedded into databricks-connect (and that&#8217;s why it is a bad idea to list both databricks-connect and pyspark in your project dependencies)</p><p>You can see it by going to .venv/lib/python3.11/site-packages:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QQ86!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QQ86!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 424w, https://substackcdn.com/image/fetch/$s_!QQ86!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 848w, https://substackcdn.com/image/fetch/$s_!QQ86!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 1272w, https://substackcdn.com/image/fetch/$s_!QQ86!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QQ86!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png" width="1392" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:1392,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QQ86!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 424w, https://substackcdn.com/image/fetch/$s_!QQ86!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 848w, https://substackcdn.com/image/fetch/$s_!QQ86!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 1272w, https://substackcdn.com/image/fetch/$s_!QQ86!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7156ba4-79d6-4a95-8dcc-7a19be1bdcc8_1392x494.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>4. Let&#8217;s try to use SparkSession instead!</strong></p><pre><code>from pyspark.sql import SparkSession  spark = SparkSession.builder.getOrCreate()  df = spark.read.table("samples.nyctaxi.trips")  df.show(5)</code></pre><p>It should not surprise you, that this would not work. Import works without issues (because pyspark is installed), but when we try to run the second line, we get the RuntimeError: &#8220;Only remote Spark sessions using Databricks Connect are supported. Use DatabricksSession.builder to create a remote Spark session instead.&#8221;</p><p>Interestingly enough, this works with VS Code Extension! Let&#8217;s get into it!</p><h3>VS Code Extension</h3><p>Let&#8217;s try out Databricks Connect now without using VS Code Extension.</p><p><strong>1.Install the extension.</strong> Follow the <a href="https://docs.databricks.com/en/dev-tools/vscode-ext/install.html">official documentation</a>.</p><p><strong>2.Open the demo project.</strong> It does not have any files except .venv folder. Click on the Databricks logo -&gt; create configuration -&gt; choose your profile.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oxir!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oxir!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 424w, https://substackcdn.com/image/fetch/$s_!oxir!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 848w, https://substackcdn.com/image/fetch/$s_!oxir!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 1272w, https://substackcdn.com/image/fetch/$s_!oxir!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oxir!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png" width="1118" height="1584" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1584,&quot;width&quot;:1118,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oxir!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 424w, https://substackcdn.com/image/fetch/$s_!oxir!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 848w, https://substackcdn.com/image/fetch/$s_!oxir!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 1272w, https://substackcdn.com/image/fetch/$s_!oxir!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33a8d1c2-d5fe-42b9-9c18-cf42f55edb36_1118x1584.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>3. All green checkmarks: </strong>Activate your environment, install pip, and select your cluster.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IGtM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IGtM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 424w, https://substackcdn.com/image/fetch/$s_!IGtM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 848w, https://substackcdn.com/image/fetch/$s_!IGtM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 1272w, https://substackcdn.com/image/fetch/$s_!IGtM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IGtM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png" width="1456" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IGtM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 424w, https://substackcdn.com/image/fetch/$s_!IGtM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 848w, https://substackcdn.com/image/fetch/$s_!IGtM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 1272w, https://substackcdn.com/image/fetch/$s_!IGtM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6440586e-94ee-4d0e-b587-9f9dea1debde_1600x809.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>4. Let&#8217;s check the files. </strong>We can see that The extension created several files, including databricks.yml file. The host in the file must match the host that is specified in the profile.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O2kN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O2kN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 424w, https://substackcdn.com/image/fetch/$s_!O2kN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 848w, https://substackcdn.com/image/fetch/$s_!O2kN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 1272w, https://substackcdn.com/image/fetch/$s_!O2kN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O2kN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png" width="1456" height="589" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:589,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!O2kN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 424w, https://substackcdn.com/image/fetch/$s_!O2kN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 848w, https://substackcdn.com/image/fetch/$s_!O2kN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 1272w, https://substackcdn.com/image/fetch/$s_!O2kN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63b717df-a181-4ae3-99fb-b50ae24d3895_1600x647.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>5. Let&#8217;s create a demo.py file</strong> (the same code we tried to run from a terminal). We&#8217;ll also add the first line &#8220;# Databricks notebook source&#8221;- and the code can be recognized as a notebook.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SnJ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SnJ-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 424w, https://substackcdn.com/image/fetch/$s_!SnJ-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 848w, https://substackcdn.com/image/fetch/$s_!SnJ-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 1272w, https://substackcdn.com/image/fetch/$s_!SnJ-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SnJ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png" width="1440" height="688" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:688,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SnJ-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 424w, https://substackcdn.com/image/fetch/$s_!SnJ-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 848w, https://substackcdn.com/image/fetch/$s_!SnJ-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 1272w, https://substackcdn.com/image/fetch/$s_!SnJ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94e0e064-9e49-442c-9e88-dc328968a252_1440x688.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This works because the extension created configuration in the .vscode folder (settings.json):</p><pre><code>{     "jupyter.interactiveWindow.cellMarker.codeRegex": "^# COMMAND ----------|^# Databricks notebook source|^(#\\s*%%|#\\s*\\&lt;codecell\\&gt;|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])",          "jupyter.interactiveWindow.cellMarker.default": "# COMMAND ----------" }</code></pre><p>The code can also run in VS Code (Spark session can be created&#8202;&#8212;&#8202;because the extension created all necessary environment variables and stored them in .databricks.env in .databricks folder.</p><p>Unfortunately, the code of databricks-connect is proprietary, and we can not tell how exactly the environment variables get evaluated.</p><p>The only thing we know, there is no magic :-)</p><h3>Conclusions</h3><p>We hope this article clarifies some most common confusion when developers try to develop outside of a Databricks environment.</p><p>Check out a <a href="https://marvelousmlops.substack.com/p/developing-on-databricks-without">related article</a> about developing on Databricks!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.marvelousmlops.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Marvelous MLOps Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>