<?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"><channel><title><![CDATA[Jonathan's blog]]></title><description><![CDATA[Mostly software projects & ideas I want to share.]]></description><link>https://jonathanadly.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 12:44:21 GMT</lastBuildDate><atom:link href="https://jonathanadly.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Is async django ready for prime time?]]></title><description><![CDATA[We have traditionally used Django in all our products. We believe it is one of the most underrated, beautifully designed, rock solid framework out there.
https://x.com/Jonathan_Adly_/status/1855357440034009362
 
However, if we are to be honest, the h...]]></description><link>https://jonathanadly.com/is-async-django-ready-for-prime-time</link><guid isPermaLink="true">https://jonathanadly.com/is-async-django-ready-for-prime-time</guid><category><![CDATA[AI]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Wed, 13 Nov 2024 15:40:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731511599652/43665b79-93ae-4189-bdde-4cf252154136.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We have traditionally used Django in all our products. We believe it is one of the most underrated, beautifully designed, rock solid framework out there.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://x.com/Jonathan_Adly_/status/1855357440034009362">https://x.com/Jonathan_Adly_/status/1855357440034009362</a></div>
<p> </p>
<p>However, if we are to be honest, the history of async usage in Django wasn't very impressive. It was always clunky, and you end up with your code cluttered with this ugly syntax.</p>
<pre><code class="lang-python">async_to_sync(some_function)(arg_1, arg_2)
</code></pre>
<p>You could argue that for most products, you don’t really need async. It was just an extra layer of complexity without any significant practical benefit.</p>
<p>Over the last couple of years, AI use-cases have changed that perception. Many AI products have calling external APIs over the network as their bottleneck. This makes the complexity from async Python worth considering. FastAPI with its intuitive async usage and simplicity have risen to be the default API/web layer for AI projects.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731510962156/2f05a97e-7e7c-4e85-ae92-bb671b06e019.png" alt class="image--center mx-auto" /></p>
<p>We watched with concern as the view of Django as a clunky async framework spread. This happened partly because large language models had outdated information and partly because there aren't any complex or large open-source projects showcasing Django's async usage.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Async code does not improve the performance of CPU-bound tasks. It enhances performance in areas where tasks are waiting for IO to complete by allowing the CPU to handle other tasks in the meantime. Again, AI use-cases are a great candidate for this as the bottleneck is usually waiting on data.</div>
</div>

<p>One of our side quests when building <a target="_blank" href="https://colivara.com">ColiVara</a> was to demonstrate Django's async features and to be completely end-to-end async. We aimed to be an <a target="_blank" href="https://github.com/tjmlabs/ColiVara">open-source project</a> that could effectively showcase Django's async capabilities.</p>
<p>As background, <a target="_blank" href="https://colivara.com">ColiVara</a> is a retrieval API that allows you to store, search, and retrieve documents based on their <strong><em>visual</em></strong> embeddings. It works exactly like RAG from the end-user standpoint - but using vision models instead of chunking and text-processing for documents.</p>
<p>It is designed as a Django API service with a separate, standalone GPU service that handles the AI workloads.</p>
<h2 id="heading-footguns">Footguns</h2>
<p>The key takeaway is that your application needs full async support to truly benefit from it. It's a commitment you make from the start, and mixing sync and async code won't give you any benefits. It will only add unnecessary complexity.</p>
<p>If your code is a mix between sync and async, Django has to mimic the other call style to make your code work. This switch causes a slight <a target="_blank" href="https://docs.djangoproject.com/en/5.0/topics/async/#performance">performance delay of about a millisecond.</a> Is it a big deal? Probably not if you have a sync call occasionally. However, if your application is a 50/50 mix, it's likely better to stick with sync code.</p>
<p>In summary, this means you must:</p>
<ul>
<li><p>Use an ASGI web server to handle requests asynchronously</p>
</li>
<li><p>Write async views</p>
</li>
<li><p><a target="_blank" href="https://docs.djangoproject.com/en/5.1/topics/db/queries/#async-queries">Use async queries in the ORM</a></p>
</li>
<li><p>Use an async HTTP client library like aiohttp for any API calls</p>
</li>
<li><p>Upgrade your custom <a target="_blank" href="https://docs.djangoproject.com/en/5.1/topics/http/middleware/#asynchronous-support">middleware to be async-compatible</a></p>
</li>
</ul>
<h3 id="heading-asgi-web-server">ASGI Web Server</h3>
<p>For a Django project, the usual choices are either <a target="_blank" href="https://github.com/django/daphne">Daphne</a> or <a target="_blank" href="https://github.com/encode/uvicorn">Uvicorn</a>. We think both have similar capabilities. We chose Uvicorn because it's lighter and more like "gunicorn." Our general rule is to use Daphne if we're working with WebSockets and Django Channels; otherwise, we go with Uvicorn.</p>
<h3 id="heading-async-views">Async views</h3>
<p>We used <a target="_blank" href="https://django-ninja.dev/">django-ninja</a> as our API library. It is newer than the well-known Django Rest Framework and focuses on performance and support for async code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731354897923/533be085-cf6b-4027-8710-2c8e9da1b311.png" alt class="image--center mx-auto" /></p>
<p>Our experience with django-ninja has been rock solid, and we recommend starting new API Django projects with it, even if you aren't writing async views or endpoints.</p>
<p>The coding style is very similar to FastAPI, but you also get all the Django features that make it "batteries-included."</p>
<h3 id="heading-async-orm">Async ORM</h3>
<p>The biggest improvement in Django over the last six months is how much the ORM now supports async code. Practically, the vast majority of operations are supported. You simply add a prefix of <code>a</code> and things work out of the box.</p>
<pre><code class="lang-python">User.objects.get(id=<span class="hljs-number">1</span>) <span class="hljs-comment"># sync</span>
User.objects.aget(id=<span class="hljs-number">1</span>) <span class="hljs-comment"># async</span>
</code></pre>
<p>One thing to be mindful of, is all the LLMs as of late 2024 are completely outdated on what is possible and what is not possible with Django async ORM operations. You should assume that their code is wrong, especially if you see it littered with <code>async_to_sync</code>.</p>
<h2 id="heading-async-api-calls">Async API calls</h2>
<p>Here is where the big benefit for async paid off for us. The architecture of <a target="_blank" href="https://colivara.com">ColiVara</a> is to call the GPU service whenever we need to do an AI workload. This is how this looks like:</p>
<pre><code class="lang-python"><span class="hljs-comment"># stuff here </span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> aiohttp.ClientSession() <span class="hljs-keyword">as</span> session:
        <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> session.post(
            EMBEDDINGS_URL, json=payload, headers=headers
        ) <span class="hljs-keyword">as</span> response:
            <span class="hljs-keyword">if</span> response.status != <span class="hljs-number">200</span>:
                <span class="hljs-comment">#handle error</span>
            response_data = <span class="hljs-keyword">await</span> response.json()
<span class="hljs-comment"># stuff here</span>
</code></pre>
<p>In this code, when our application hits the <code>await</code> keyword, it can yield control back to the event loop, allowing other tasks to run while waiting for the HTTP response.</p>
<p>Here's what happens:</p>
<ol>
<li><p>When the code reaches the POST request, it doesn't block</p>
</li>
<li><p>While waiting for the response from <code>EMBEDDINGS_URL</code>, the event loop can:</p>
<ul>
<li><p>Handle other incoming requests</p>
</li>
<li><p>Process other async tasks</p>
</li>
<li><p>Run other coroutines</p>
</li>
</ul>
</li>
<li><p>When the response comes back, our code resumes from where it left off</p>
</li>
</ol>
<p>This is one of the main benefits of async programming - it allows for concurrent execution without using multiple threads or processes, particularly efficient for I/O-bound operations like HTTP requests.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">A simple analogy: It's like ordering food at a restaurant. Instead of standing at the counter waiting for your order (blocking), you sit at your table and can do other things (check email, chat) while waiting for your food to be ready.</div>
</div>

<p>Additionally, we can easily process items concurrently which leads to a big performance boost.</p>
<pre><code class="lang-python"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_document</span>(<span class="hljs-params">document</span>):</span>
    <span class="hljs-comment"># stuff here</span>
    <span class="hljs-keyword">return</span> 

documents = [<span class="hljs-string">'A'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'C'</span>, <span class="hljs-string">'D'</span>]
<span class="hljs-comment"># Process all items concurrently</span>
results = <span class="hljs-keyword">await</span> asyncio.gather(
        *[process_document(document) <span class="hljs-keyword">for</span> document <span class="hljs-keyword">in</span> documents]
    )
</code></pre>
<h2 id="heading-async-middleware">Async middleware</h2>
<p>One of the main challenges with async Django is middleware. If you use synchronous middleware between an ASGI server and an async view, it switches to sync mode for the middleware and back to async for the view, causing overhead. Designing middleware to work both async and sync is simple; just be mindful of it. Here's a basic middleware that adds a slash to API requests as an example.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> asgiref.sync <span class="hljs-keyword">import</span> iscoroutinefunction
<span class="hljs-keyword">from</span> django.utils.decorators <span class="hljs-keyword">import</span> sync_and_async_middleware


<span class="hljs-comment"># add slash middleware</span>
<span class="hljs-meta">@sync_and_async_middleware</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_slash</span>(<span class="hljs-params">get_response</span>):</span>
    <span class="hljs-keyword">if</span> iscoroutinefunction(get_response):
        <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">middleware</span>(<span class="hljs-params">request</span>):</span>
            <span class="hljs-comment"># we want to leave openapi, swagger and redoc as is</span>
            keep_as_is = any(
                x <span class="hljs-keyword">in</span> request.path <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> [<span class="hljs-string">"openapi"</span>, <span class="hljs-string">"swagger"</span>, <span class="hljs-string">"redoc"</span>, <span class="hljs-string">"docs"</span>]
            )
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> request.path.endswith(<span class="hljs-string">"/"</span>) <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> keep_as_is:
                request.path_info = request.path = <span class="hljs-string">f"<span class="hljs-subst">{request.path}</span>/"</span>
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> get_response(request)
    <span class="hljs-keyword">else</span>:
        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">middleware</span>(<span class="hljs-params">request</span>):</span>
            keep_as_is = any(
                x <span class="hljs-keyword">in</span> request.path <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> [<span class="hljs-string">"openapi"</span>, <span class="hljs-string">"swagger"</span>, <span class="hljs-string">"redoc"</span>, <span class="hljs-string">"docs"</span>]
            )
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> request.path.endswith(<span class="hljs-string">"/"</span>) <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> keep_as_is:
                request.path_info = request.path = <span class="hljs-string">f"<span class="hljs-subst">{request.path}</span>/"</span>
            <span class="hljs-keyword">return</span> get_response(request)

    <span class="hljs-keyword">return</span> middleware
</code></pre>
<p>Django will automatically pick the sync vs. the async path depending on the request.</p>
<p>We recommend thoroughly reviewing the middleware you use for async Django to ensure they support async usage. The switch overhead is generally minimal, but it could become problematic if left unchecked.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We believe async Django is ready for production. In theory, there should be no performance loss when using async Django instead of FastAPI for the same tasks. Django's built-in features greatly simplify and enhance the developer experience. Using async involves some complexity, as the entire code path must be async. In AI workloads, this complexity is often a worthwhile tradeoff.</p>
]]></content:encoded></item><item><title><![CDATA[My UV Docker workflow]]></title><description><![CDATA[“uv” is a newish Python package installer and resolver. It is a nice balance between the simplicity of plain old venv, and the complexity of poetry. The team behind so far, had made the right opinionated choices and I believe it will continue to grow...]]></description><link>https://jonathanadly.com/my-uv-docker-workflow</link><guid isPermaLink="true">https://jonathanadly.com/my-uv-docker-workflow</guid><category><![CDATA[Python]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Fri, 04 Oct 2024 14:37:07 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://docs.astral.sh/uv/">“uv”</a> is a newish Python package installer and resolver. It is a nice balance between the simplicity of plain old venv, and the complexity of poetry. The team behind so far, had made the right opinionated choices and I believe it will continue to grow.</p>
<p>I decided to migrate a few projects to it. Some super simple, but one that was particularly complex with platform-specific requirements with CUDA and Pytorch. The documentations gives many options, which can make a migration overwhelming with the paralysis of choice. Additionally, uv does not yet generate a platform-agnostic lockfile, so there are a couple of things to watch out for in complex OS-specific projects.</p>
<p>Here is where I ended up, where I feel it is a nice balance between keeping the same simple pip workflows, but gaining the speed of uv with a straightforward 2 min migration process.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:[preferred image]
<span class="hljs-comment"># your normal setup up to pip install</span>

<span class="hljs-comment"># 1. install uv inside of Docker</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv</span>
<span class="hljs-comment"># 2. Copy your requirements to Docker </span>
<span class="hljs-keyword">COPY</span><span class="bash"> requirements.in .</span>
<span class="hljs-comment"># 3. compile your requirements</span>
<span class="hljs-keyword">RUN</span><span class="bash"> uv pip compile requirements.in -o requirements.txt</span>
<span class="hljs-comment">#4. Install the new compiled requirements, specific to the Docker platform </span>
<span class="hljs-keyword">RUN</span><span class="bash"> uv pip sync requirements.txt --no-cache-dir --compile-bytecode --system</span>

<span class="hljs-comment"># the rest of Dockerfile</span>
</code></pre>
<p>The key changes are really simple 4 lines of code, and using a requirements.in file for your dependencies.</p>
<p>Simple and optional optimizations are:</p>
<ol>
<li>—compile-bytecode</li>
</ol>
<blockquote>
<p><code>--compile-bytecode</code><br />Compile Python files to bytecode after installation.</p>
<p>By default, uv does not compile Python (<code>.py</code>) files to bytecode (<code>__pycache__/*.pyc</code>); instead, compilation is performed lazily the first time a module is imported. For use-cases in which start time is critical, such as CLI applications and Docker containers, this option can be enabled to trade longer installation times for faster start times.</p>
</blockquote>
<ol start="2">
<li>—no-cache-dir</li>
</ol>
<blockquote>
<p><code>--no-cache-dir</code><br />The --no-cache-dir option tells pip to not save the downloaded packages locally, since we are in an ephemeral container. The cache doesn’t persist anyway.</p>
</blockquote>
<ol start="3">
<li>—system</li>
</ol>
<blockquote>
<p>By default, uv installs into the virtual environment in the current working directory or any parent directory. The <code>--system</code> option instructs uv to instead use the first Python found in the system <code>PATH</code>.</p>
</blockquote>
<p>Typically - a requirements.in file will be whatever you actually import in your project. For example, in our complex Pytorch and CUDA projects - this was the requirements.in:</p>
<pre><code class="lang-dockerfile">colpali-engine==<span class="hljs-number">0.3</span>.<span class="hljs-number">1</span>
runpod==<span class="hljs-number">1.7</span>.<span class="hljs-number">0</span>
Pillow==<span class="hljs-number">10.4</span>.<span class="hljs-number">0</span>
</code></pre>
<p>Those 3 “simple” dependencies when compiled - generate 120+ other packages that are super fragile and OS dependent. This is all abstracted and taken care of though with uv - with all the action happening at the Dockerfile build time.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">For complex projects, you should specify the platform during the Docker build time (this is typically a must with CUDA/Pytorch projects). For example, if your production is a linux environment, you would build using the flag: <code>--platform linux/amd64</code> or use the <code>platform</code> option in docker-compose. So, you exactly match everything between development and production. This is the case with or without uv.</div>
</div>

<p>This workflow has been in production for a couple of weeks now, with no issues. It was relatively painless and quick. The biggest gain is probably around developer experience where having a simple requirements.in file allows for quick upgrades and confidence that nothing will break accidentally.</p>
]]></content:encoded></item><item><title><![CDATA[Hyperscript Behaviors]]></title><description><![CDATA[Hyperscript is a really fun way to add little event-driven scripts to your web application. It is one of the best ways to enforce locality of behavior in a hypermedia-first application. It also solves a bunch of problems with async behavior in native...]]></description><link>https://jonathanadly.com/hyperscript-behaviors</link><guid isPermaLink="true">https://jonathanadly.com/hyperscript-behaviors</guid><category><![CDATA[hypermedia]]></category><category><![CDATA[hyperscript]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Fri, 13 Sep 2024 13:51:27 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://hyperscript.org/">Hyperscript</a> is a really fun way to add little event-driven scripts to your web application. It is one of the best ways to enforce <a target="_blank" href="https://htmx.org/essays/locality-of-behaviour/">locality of behavior</a> in a <a target="_blank" href="https://hypermedia.systems/">hypermedia-first application</a>. It also solves a bunch of problems with async behavior in native JS by doing things “<strong>without promises, async / await or callback hell”</strong>.</p>
<p>Just like tailwinds enforces locality of behavior for CSS with some trade-offs, hyperscript does the same for vanilla JavaScript.</p>
<p>One gripe I always had with hyperscript is re-inventing the wheel whenever I tried do implement a common UI pattern. Like tabs, modals, file uploads, etc. It is not particularly hard. It is just a task that someone has perfected over the last 15 years in vanilla JS or the JS frameworks, and now - I have to write my own version of the very same thing.</p>
<p>That’s why I was super excited when I found this <a target="_blank" href="https://benpate.github.io/hyperscript-widgets/">gem</a>. A beautifully built collection of common hyperscript widgets that can be copied wholesale or easily customized for your own application.</p>
<p>The way to “install” these is by right clicking, view source, and copying the code. A <a target="_blank" href="https://htmx.org/essays/right-click-view-source/">core tenet</a> of the culture around hyperscript.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726232327282/5b564a77-80ba-48c2-9256-90c1211227a1.gif" alt class="image--center mx-auto" /></p>
<p>The way these widgets are implemented are by defining <a target="_blank" href="https://hyperscript.org/features/behavior/"><em>hyperscript behaviors</em>.</a> The Don’t Repeat Yourself (DRY) implementation in hyperscript. You define a behavior once - put it online if you are a kind soul. Then, no one else will have to reinvent the wheel.</p>
<p>This was a great find, and I will try to add my own behaviors to the same repo as I have the time. In the meantime, you don’t have to reinvent the wheel if you use hyperscript - right click, view source, and enjoy!</p>
]]></content:encoded></item><item><title><![CDATA[Long Context vs. RAG]]></title><description><![CDATA[One of the projects I have built is a long-standing retrieval-augmented generation (RAG) application. Documents are saved in a database, chunked into a reasonable amount of text that a large language model (LLM) can handle, and turned into numerical ...]]></description><link>https://jonathanadly.com/long-context-vs-rag</link><guid isPermaLink="true">https://jonathanadly.com/long-context-vs-rag</guid><category><![CDATA[AI]]></category><category><![CDATA[RAG ]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Wed, 04 Sep 2024 22:13:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725487848818/72a6fac5-60b6-4056-a4d4-198710dc1784.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the projects I have built is a long-standing retrieval-augmented generation (RAG) application. Documents are saved in a database, chunked into a reasonable amount of text that a large language model (LLM) can handle, and turned into numerical representation (vectors).</p>
<p>A user at some point later asks a question that get turned also into a numerical representation. We compare the numbers and do some math to identify the top k(3) chunks of texts that matches the question. This is feed that into an LLM, and we get an answer based on the documents uploaded.</p>
<p>RAG implementations are notorious for being unscientific in nature, and more art than science. How do you transform a PDF into text? What about tabular data? What if there are pictures in the document and they are pretty important? How long should the chunks be? How many chunks? Should you use Cosine similarity for the math or something else? and a million other questions where the correct answers is always "it depends".</p>
<p>I am and you should be highly suspicious of generic "one size fits all" RAG solutions. The really good ones customize for the use-case, user types, and the documents.</p>
<p>But, that's all irrelevant now, I am moving the project to use long-context instead because I think no one should be building RAG products in 2024 - and I will explain why below.</p>
<h2 id="heading-rag-is-a-workaround">RAG is a workaround</h2>
<p>In 2021/2022 - when I first started building on LLMs we had GPT3.5 and ~4000 context length. LLMs without guardrails hallucinated at unacceptable rate for anything that is accuracy-critical.</p>
<p>Adding a knowledge base solved hallucination to a large extent. But, you were only limited to 4000 words. Many real-world problems required documents that are larger than the model context. So, RAG become the standard solutions.</p>
<p>Break down large documents into smaller pieces and use semantic search techniques to get the relevant parts, instead of the whole documents. A clever solution to the problem of context length and hallucination.</p>
<p>Now - we have neither of these problems. Gemini Pro handles 1m+ tokens with no issues, and and LLMs hallucinate a lot less. Some have seen degradation in GPT-4 after 16k tokens, but this more of a GPT-4 limitations than all LLMs.</p>
<p>This is my favorite formal eval (<a target="_blank" href="https://github.com/hsiehjackson/RULER">link</a>) for the "real context size" for LLMs and we can clearly sees that context degradation is model-specific and not an LLM limitation.</p>
<p>In summary - we used RAG because we had a problem that no longer exists.</p>
<h2 id="heading-rag-is-less-performant-than-long-context">RAG is less performant than long-context</h2>
<p>As of late July 2024, we had pretty solid evidence and evals that long-context beats most RAG implementations. I like this <a target="_blank" href="https://arxiv.org/abs/2407.16833">paper</a> as it proves this point, even though it tries to tell you to use some RAG.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725484867753/5bc78395-8816-4974-9aa0-0957603365af.png" alt class="image--center mx-auto" /></p>
<p>If you care about performance, then you absolutely should take away RAG from your stack.</p>
<h2 id="heading-kiss-keep-it-simple-always-wins">KISS (Keep it simple) always wins</h2>
<p>Some people argue that inference cost is a factor, and you should consider RAG for some queries (like the paper above). Nope. Bad idea.</p>
<ol>
<li><p>LLM inference costs are coming down rapidly</p>
</li>
<li><p>The development cost (AKA all the engineers you need to hire to maintain your bespoke RAG solutions) is a lot more expensive than the API costs</p>
</li>
<li><p>Large RAG applications are very difficult to maintain and iterate on, because they are fragile and small changes can lead to big changes.</p>
</li>
</ol>
<p>Long-context applications on the other hand are very simple. You just worry about getting the text out of documents correctly and everything else works via calling an API. No chunking, no vectors, no math. Easy to maintain, easy to iterate on, and as simple as it gets.</p>
<h2 id="heading-the-unpopular-opinion">The unpopular opinion</h2>
<p>Simplicity is great, but it doesn't sell. So, there would be a lot of noise and disagreement to folks invested in the RAG ecosystem. I get it. If you just raised a bunch of money for a vector DB, you aren't a big fan of this new development.</p>
<p>I ran a large RAG application in production and have been for the last 2.5 years. My skill-set and experience in that corner of development are essentially obsolete. But, it is the nature of AI engineering and being on the bleeding edge.</p>
]]></content:encoded></item><item><title><![CDATA[Securing Your Self-Hosted Open Source AI Application]]></title><description><![CDATA[I've got some cool tools—both AI and non-AI, open-source—that I absolutely love using on my local machine. One tool that I really wanted to move to the cloud, is the screenshot-to-code tool. It makes life so much easier for designers to developers ha...]]></description><link>https://jonathanadly.com/securing-your-self-hosted-open-source-ai-application</link><guid isPermaLink="true">https://jonathanadly.com/securing-your-self-hosted-open-source-ai-application</guid><category><![CDATA[#ai-tools]]></category><category><![CDATA[AI]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[self-hosted]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Sun, 07 Jul 2024 17:17:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720372211378/3c60472a-0309-441c-b95c-c260656e483f.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've got some cool tools—both AI and non-AI, open-source—that I absolutely love using on my local machine. One tool that I really wanted to move to the cloud, is the <a target="_blank" href="https://github.com/abi/screenshot-to-code">screenshot-to-code</a> tool. It makes life so much easier for designers to developers hand-off.</p>
<p>I wanted them to use screenshot-to-code to whip up code templates directly, instead of just handing over Figma designs. They're savvy enough to tweak Tailwind classes and work with AI prompts. But let's be real, setting up a GitHub repository, messing with Docker, and managing Python environments? That’s a bit much to ask.</p>
<p>One problem with hosting AI tools in the cloud is managing access. You have to think about security and who can do what. After all, those AI API calls aren't free, and we can't just let everyone in.</p>
<p>I also needed a simple, quick method to manage who gets in and who doesn’t. And with a bunch of other tools to host, the last thing I wanted was to drown in a massive, 200-line Nginx config file.</p>
<p>That’s where Cloudflare Zero-Trust swoops in to save the day!</p>
<h1 id="heading-tools-and-concepts">Tools and Concepts:</h1>
<p>When you're gearing up to host an open source project in the cloud, there are a handful of tools and concepts that really come in handy. I'm going to walk you through some of the essentials. Keep in mind, my recommendations are based on personal experience, so they might be a bit subjective.</p>
<ol>
<li><p><strong>Cloud Server:</strong> getting a computer from a cloud provider</p>
</li>
<li><p><strong>SSH and Vs Code:</strong> Accessing this computer from vs code</p>
</li>
<li><p><strong>Setting up project:</strong> Setup the project on the cloud server</p>
</li>
<li><p><strong>Cloudflare Site setup:</strong> Setting up your site in cloudflare</p>
</li>
<li><p><strong>Cloudflare Tunnel:</strong> Getting your server to tunnel traffic to Cloudflare</p>
</li>
<li><p><strong>Cloudflare Access Control:</strong> Setting up access rules</p>
</li>
<li><p><strong>Recipe:</strong> Step by step directions.</p>
</li>
</ol>
<p>If you're already familiar with these concepts, feel free to jump straight to the Recipe section where you can follow the practical steps to get your project live.</p>
<h2 id="heading-cloud-server">Cloud server</h2>
<p>Alright, let's dive into the cloud server setup. Once you get a taste of hosting your own tools, I bet you'll be hooked and start self-hosting everything! The first move? Grab yourself a server from a cloud provider.</p>
<p>I'm a fan of Hetzner, but honestly, any provider will do the trick and the setup steps are pretty similar. You just need a <strong>virtual private server</strong> (VPS). A computer in the cloud so to speak.</p>
<p>First things first, head over to <a target="_blank" href="http://accounts.hetzner.com/signUp">accounts.hetzner.com/signUp</a> to open your Hetzner account.</p>
<p>Now, if you’re tuning in from the U.S., brace yourself for a bit of a song and dance—it’s almost like signing up for a mortgage! Just kidding, but you will need to pay invoices in advance or setup direct bank transfers.</p>
<p>Once you're in, navigate to Hetzner Cloud from your dashboard, kick off a new project, and get ready to add a server. Here’s how I set mine up:</p>
<ul>
<li><p><strong>Location:</strong> Pick the closest one to you. For me, that was Ashburn, VA, in the US East.</p>
</li>
<li><p><strong>Image:</strong> You can go with Ubuntu OS or, if you’re planning to use Docker, jump over to Apps and pick Docker CE for the latest Ubuntu with Docker Community Edition already installed. Of course, you can always install Docker on your own later.</p>
</li>
<li><p><strong>Type:</strong> I went with a Shared cVPU x86 (Intel/AMD) CPX11 - sporting 2GB RAM and a 40GB SSD.</p>
</li>
<li><p><strong>Networking:</strong> Opt for both IPv4 and IPv6. Skipping IPv4 to save a penny might sound tempting, but trust me, you’ll need it.</p>
</li>
<li><p><strong>SSH Keys:</strong> Definitely add an SSH key. If you haven’t done this before, Hetzner has a solid <a target="_blank" href="https://community.hetzner.com/tutorials/howto-ssh-key">guide</a> on generating SSH keys. This will allow you go into the server from anywhere. So, make sure to keep it secure and in accessible location. You will need the path to private key in the next step.</p>
</li>
</ul>
<p>For now, you can skip setting up volumes, firewalls, backups, placement groups, labels, and cloud configs. You can always circle back to those later if you need them.</p>
<p>Give your serve a name and then hit ‘Create &amp; Buy Now’ and you're all set to start your self-hosting adventure!</p>
<h2 id="heading-ssh">SSH</h2>
<p>I won't go to all the intricacies of SSH here. Our goal is simple. Hit a button, and be able to jump into your cloud server with VS code all setup for you there. Our ideal world is you can't tell the difference between your local computer, and the one in the cloud. Here’s how you get it all set up:</p>
<ol>
<li><p><strong>Install the SSH Extension</strong>: First up, you need the right tool for the job. Open VS Code, head over to the Extensions view by clicking on the square icon on the sidebar. Search for "Remote-SSH" and install it. This extension is your golden ticket to connecting directly to your remote server.</p>
</li>
<li><p><strong>Create or Edit the SSH Config File</strong>: You need an SSH configuration file to connect via hitting a button. In your VS code, open Command Palette <code>Ctrl+Shift+P</code> or on Mac <code>command+shift+P</code> . Choose Remote SSH: Open SSH Configuration File</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720361884184/4d0c190d-33be-406a-aeff-8a3005789d8a.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Add Your Server Details</strong>: Open this config file in any text editor, and add a block for your server like this:</p>
<pre><code class="lang-plaintext"> Host my-server
     HostName your-server-ip 
     User your-username
     IdentityFile ~/path/to/your/private/key
</code></pre>
<p> Host is whatever you decide to call your server. HostName is the server public IP address - available from the Hetzner dashboard. The user is typically <code>root</code> for Ubuntu servers. The IdentityFile is where your SSH private key is saved on your local machine.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720369679200/0370715d-798e-4457-ba49-21943f6b254d.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Connect Using VS Code</strong>: Now, open VS Code, launch the Command Palette (<code>Ctrl+Shift+P</code>), and type ‘SSH: Connect to Host’. Instead of entering all the details, you’ll just see ‘my-server’ (or whatever nickname you gave your server) listed there. Click it, and VS Code will use the details from your config file to log you in automatically—no hassle required.</p>
</li>
<li><p><strong>Enjoy Seamless Access</strong>: And there you have it! With your SSH config file in place, connecting to your remote server becomes a breeze. Just a couple of clicks, and you’re in, ready to get down to business. Whatever you can do in your local machine, you can do in the remote server.</p>
</li>
</ol>
<h2 id="heading-setting-up-the-project">Setting up the project</h2>
<p>This is a pretty straightforward and easy step with one simple rule. <strong>Whatever you do in your local machine to setup an open-source project. Do it in the remote server after you SSH</strong>.</p>
<p>For all intents and purposes, it is just another computer and works the same way. For example, setting up screenshot-to-code. Here’s how you’d kick things off:</p>
<p>First up, make sure you've got Docker, Docker Compose, and Git on your server. Sometimes, depending on your cloud provider and the server setup you chose, these might already be installed. If not, no sweat—the Docker and Git websites have super straightforward instructions on how to get them up and running.</p>
<ol>
<li><p>Git clone the package.</p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://github.com/abi/screenshot-to-code.git
</code></pre>
</li>
<li><p>Spin the docker image up.</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">"OPENAI_API_KEY=sk-your-key"</span> &gt; .env
 docker-compose up -d --build
</code></pre>
</li>
</ol>
<p>And just like that, if you head over to <code>&lt;server-ip&gt;:5173</code> in your web browser, you’ll see your application live and in action.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If you can't access it, run docker-compose logs. It will show you if there any errors in the setup.</div>
</div>

<p>Congratulations! You’ve just successfully hosted your first open-source application. Pretty cool, right?</p>
<p>Here’s the good news. You can access this tool from anywhere in the world, and so can your team.</p>
<p>And the not-so-good news? Well, technically, anyone in the world could access it too. So, let's make sure we handle that access wisely in the next steps!</p>
<h2 id="heading-cloudflare-site-setup">Cloudflare Site Setup</h2>
<p>Alright, let’s get your application locked down with Cloudflare! Managing who gets to access your app is super important, and for that, you'll need to add a new site to Cloudflare. Plus, having a domain on Cloudflare not only ticks off a technical requirement but also keeps things neat—because let’s face it, memorizing IP addresses isn’t fun.</p>
<p>First, you will need a cloudflare account, you can sign up here: <a target="_blank" href="https://dash.cloudflare.com/sign-up">https://dash.cloudflare.com/sign-up</a></p>
<p>Don’t have a domain name yet? No worries—you can snag one directly through <a target="_blank" href="https://developers.cloudflare.com/registrar/get-started/register-domain/">Cloudflare Registrar</a>. If you’ve already got a domain, perfect! You can use it here without any hassle.</p>
<p>Now, the steps to add a new site vary a bit depending on your domain registrar, but here’s a link to Cloudflare’s detailed <a target="_blank" href="https://developers.cloudflare.com/fundamentals/setup/manage-domains/add-site/">documentation</a> on how to do add a new site. The exact steps depends on who is your domain registrar. The free tier should be just fine for this project.</p>
<p>When your done, your dashboard should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720364939265/91dcf45c-8eba-4038-8b50-ba6f12bf158c.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-cloudflare-tunnel">Cloudflare Tunnel</h2>
<p>Here is the overall idea behind cloudflare tunnel in a simple way. You download something called "cloudflared" on your server. This opens up a connection to Cloudflare servers - a "tunnel". Once the tunnel is established, Cloudflare acts as a middleman. It securely routes traffic from the internet through the tunnel to your server, down to the port number.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720365739106/87ff0dcb-fbca-4850-8057-c09ba74e5bce.webp" alt class="image--center mx-auto" /></p>
<p>Here is the steps to get it.</p>
<ol>
<li><p>Configure Zero Trust for your Cloudflare Account. Again, the free tier is fine for our use-case</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720365939318/210dafda-2846-4fda-8f43-f1414431d81e.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li><p>Go to <strong>Networks</strong> &gt; <strong>Tunnels</strong> from the Zero Trust dashboard after the initial setup</p>
</li>
<li><p>Select <strong>Create a tunnel</strong>.</p>
</li>
<li><p>Choose <strong>Cloudflared</strong> for the connector type and select <strong>Next</strong>.</p>
</li>
<li><p>Enter a name for your tunnel. This is a tunnel to your server, so make sure the name reflects that.</p>
</li>
<li><p>Select <strong>Save tunnel</strong>.</p>
</li>
<li><p>Next, you will need to install <code>cloudflared</code> and run it in your remote server.</p>
</li>
<li><p>SSH into the remote machine as described before.</p>
</li>
<li><p>If you are on Hetzner and used a Ubuntu server, copy and paste this command. You can find that command in the Tunnel dashboard for different type of operating systems. It will take a few mins until your tunnel is active.</p>
</li>
</ol>
</li>
</ol>
<pre><code class="lang-bash">    curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb &amp;&amp; 

    sudo dpkg -i cloudflared.deb &amp;&amp; 

    sudo cloudflared service install &lt;token from Zero trust tunnel setup&gt;
</code></pre>
<ol start="10">
<li><p>Once the command has finished running, your connector will appear in Zero Trust. Select <strong>Next</strong> to continue to the next step.</p>
</li>
<li><p>In the <strong>Public Hostnames</strong> tab, choose the <strong>Domain</strong> that you setup before. I recommend specifying a <strong>subdomain</strong> for each open-source project you are hosting. For example: <code>screenshot-to-code.your_domain.com</code><em>.</em> That way you can use the same server and domain for 100's of projects.</p>
</li>
<li><p>Specify a service, for example, the screenshot-to-code project uses port 5173. So, it would be <code>http://localhost:5173</code>.</p>
<ol>
<li>If you have another project on port:8001, then you just create another public hostname. <code>new-project.your_domain.com</code> and the service would be <code>http://localhost:8001</code> - no need for a new setup or servers!</li>
</ol>
</li>
<li><p>Select <strong>Save tunnel</strong>.</p>
</li>
</ol>
<p>    And that's it. Now, if you navigate to <code>screenshot-to-code.your_domain.com</code>, you should be able to see your application. No Nginx or SSL setup needed. Cloudflare took care of all of these for us.</p>
<p>    We still have a problem though. Our application is still wide-open to anyone who manages to access the domain name. This what Cloudflare Access Control fixes.</p>
<h2 id="heading-cloudflare-access-control">Cloudflare Access Control</h2>
<p>So, our mission is pretty clear here: We only want the right people getting into our application. Here’s how we’ll make sure of that:</p>
<p>When someone heads over to our app at <code>screenshot-to-code.your-domain.com</code>, Cloudflare steps in and greets them with a login page. All they need to do is punch in their email.</p>
<p>Now, if their email is on our VIP list, they’ll get a PIN number sent right to it. Pop in that PIN, and voilà, they’re in! If not, no access—simple as that.</p>
<p>Down the road, we can jazz things up with all sorts of fancy authentication methods—think single sign-on, tokens, even hardware solutions, complete with custom rules to fit our needs. But for now, this email and PIN setup does the trick quite nicely.</p>
<p>Here is steps to do that.</p>
<ol>
<li><p>Go to <strong>Access</strong> &gt; <strong>Applications</strong> from the Zero Trust dashboard</p>
</li>
<li><p>Click on <strong>Add an Application</strong> button</p>
</li>
<li><p>Click on <strong>Self-hosted</strong> option</p>
</li>
<li><p>Give your application a name. For example, <code>screenshot-to-code</code> and how long the access sessions are good for.</p>
</li>
<li><p>Add your domain and subdomain</p>
</li>
<li><p>Keep everything else the same and move to policies.</p>
</li>
<li><p>Give your policy a name <code>email-rule</code> for example. And under <strong>Configure Rules,</strong> add <strong>Selector</strong> to be email and add the emails of the users you want to have access.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720368760492/c30479f8-549a-4297-b294-1f3de1df4c81.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Click on the <strong>Authentication</strong> tab, and set the Authentication to be <strong>One Time Pin</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720368978254/45aee889-ad48-4c89-a92d-b32b91f88da9.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Keep the default <strong>Settings</strong> without any changes.</p>
</li>
<li><p>Save your Applications</p>
</li>
</ol>
<p>And that's it. Now, if you navigate to <code>screenshot_to_code.your-domain.com</code> you will be able to see Cloudflare default log in page. Only the people who you gave access to will be able to log in and access the application.</p>
<p>For everyone else - they can't access your application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720369214884/7d43fe61-4f31-43ba-abf4-1d36c128fc9d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-recipe">Recipe:</h2>
<p>The goal of this recipe is self-host an open-source AI application on the cloud in a secure manner. Specifically to restrict access based access rules and policies. We are using the <a target="_blank" href="https://github.com/abi/screenshot-to-code">screenshot-to-code</a> as an example.</p>
<ol>
<li><p>Start by getting a VPS if you don't have one already. Make sure you can access it via SSH.</p>
</li>
<li><p>Make sure <strong>Docker</strong>, <strong>docker-compose</strong> and <strong>git</strong> are installed on your VPS</p>
</li>
<li><p>SSH into the machine and setup the screenshot-to-code project</p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://github.com/abi/screenshot-to-code.git 
 <span class="hljs-built_in">echo</span> <span class="hljs-string">"OPENAI_API_KEY=sk-your-key"</span> &gt; .env
 docker-compose up -d --build
</code></pre>
</li>
<li><p>Make sure project is working with no errors by navigating to &lt;public_ip&gt;:5173 in your browser or <code>docker-compose logs</code> in your terminal</p>
</li>
<li><p>Set up a new Cloudflare Site. You will need a cloudflare account and a domain name.</p>
</li>
<li><p>Setup <strong>Zero Trust</strong> for your Cloudflare Account. The free tier is fine for our use-case. You will need to add your credit card though.</p>
</li>
<li><p>Create a cloudflare <strong>tunnel</strong> on your <strong>Zero Trust</strong> dashboard</p>
<ol>
<li><p>Go to <strong>Networks</strong> &gt; <strong>Tunnels</strong> from the Zero Trust dashboard after the initial setup</p>
</li>
<li><p>Select <strong>Create a tunnel</strong>.</p>
</li>
<li><p>Choose <strong>Cloudflared</strong> for the connector type and select <strong>Next</strong>.</p>
</li>
<li><p>Enter a name for your tunnel. This is a tunnel to your server, so make sure the name reflects that.</p>
</li>
<li><p>Select <strong>Save tunnel</strong></p>
</li>
</ol>
</li>
<li><p>Connect your <strong>VPS</strong> to the tunnel by installing <strong>cloudflared</strong></p>
<ol>
<li><p>SSH into the remote machine</p>
</li>
<li><p>If you are on a Ubuntu server, copy and paste this command. You can find that command in the Tunnel dashboard for different type of operating systems. It will take a few mins until your tunnel is active.</p>
</li>
<li><p>Once the command has finished running, your connector will appear in Zero Trust. Select <strong>Next</strong> to continue to the next step</p>
</li>
</ol>
</li>
</ol>
<pre><code class="lang-bash">    curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb &amp;&amp; 

    sudo dpkg -i cloudflared.deb &amp;&amp; 

    sudo cloudflared service install &lt;token from Zero trust tunnel setup&gt;
</code></pre>
<ol start="9">
<li><p>Setup your application traffic to be sent via the tunnel</p>
<ol>
<li><p>In your <strong>Tunnel</strong> setup, under the <strong>Public Hostnames</strong> tab, choose a <strong>Domain</strong> that you setup before as part of setting a Cloudflare Website. Optionally, specify a <strong>subdomain</strong> for each open-source project you are hosting. For example: <code>screenshot-to-code.your_domain.com</code><em>.</em> That way you can use the same server and domain for other projects.</p>
</li>
<li><p>Specify a service, for example, the screenshot-to-code project uses port 5173. So, it would be <code>http://localhost:5173</code>.</p>
</li>
<li><p>Select <strong>Save tunnel</strong>.</p>
</li>
</ol>
</li>
<li><p>Lock your application behind Cloud Flare Access Control</p>
<ol>
<li><p>Go to <strong>Access</strong> &gt; <strong>Applications</strong> from the Zero Trust dashboard</p>
</li>
<li><p>Click on <strong>Add an Application</strong> button</p>
</li>
<li><p>Click on <strong>Self-hosted</strong> option</p>
</li>
<li><p>Give your application a name. For example, <code>screenshot-to-code</code> and how long the access sessions are good for.</p>
</li>
<li><p>Add your domain and subdomain</p>
</li>
<li><p>Keep everything else the same and move to policies.</p>
</li>
<li><p>Setup an access policy by specifying a <strong>name</strong> <code>email-rule</code> for example. And under <strong>Configure Rules,</strong> add <strong>Selector</strong> to be email and add the emails of the users you want to have access.</p>
</li>
<li><p>Click on the <strong>Authentication</strong> tab, and set the Authentication to be <strong>One Time Pin</strong></p>
</li>
<li><p>Keep the default <strong>Settings</strong> without any changes.</p>
</li>
<li><p>Save your Applications</p>
</li>
</ol>
</li>
</ol>
<p>And that's it. Now, if you navigate to <code>screenshot_to_code.your-domain.com</code> you will be able to see Cloudflare default log in page. Only the people who you gave access to will be able to log in and access the application.</p>
<p>Congratulations, you have self-hosted your first open-source project with all what you need for future security concerns and scaling.</p>
]]></content:encoded></item><item><title><![CDATA[Ollama with Llama3 and Code Interpreter]]></title><description><![CDATA[I try to run an experiment once a week with open-source LLMs. This week experiment was using Llama3 via Ollama and AgentRun to have an open-source, 100% local Code Interpreter.
The idea is, give an LLM a query that is better answered via code executi...]]></description><link>https://jonathanadly.com/ollama-with-llama3-and-code-interpreter</link><guid isPermaLink="true">https://jonathanadly.com/ollama-with-llama3-and-code-interpreter</guid><category><![CDATA[Llama3]]></category><category><![CDATA[Python]]></category><category><![CDATA[ollama]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Fri, 26 Apr 2024 18:53:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714157385263/f915616e-6afe-4ee6-8d97-88eab2e3e41d.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I try to run an experiment once a week with open-source LLMs. This week experiment was using <a target="_blank" href="https://llama.meta.com/llama3/">Llama3</a> via <a target="_blank" href="https://ollama.com/">Ollama</a> and <a target="_blank" href="https://github.com/Jonathan-Adly/AgentRun">AgentRun</a> to have an open-source, 100% local Code Interpreter.</p>
<p>The idea is, give an LLM a query that is better answered via code execution instead of its training. Run the code in AgentRun, then return the answer to the user. It is more or less a proof of concept, that can be expanded on with additional tools that an LLM can use.</p>
<p>For this experiment, I had Ollama installed and running as well as the AgentRun API. My goal was use code generated by an LLM it to answer some questions that normally an LLM would struggle with. Like, what is 12345 * 54321? Or what is the largest prime number under 1000?</p>
<p>The full code is available here: <a target="_blank" href="https://jonathan-adly.github.io/AgentRun/examples/ollama_llama3/">https://jonathan-adly.github.io/AgentRun/examples/ollama_llama3/</a></p>
<h1 id="heading-step-1-setting-up">Step 1: Setting Up</h1>
<p>If you don't have Ollama installed, first install it from <a target="_blank" href="https://ollama.com/">here</a>. Then, run a test query to make sure everything is working.</p>
<pre><code class="lang-bash">curl -X POST http://localhost:11434/api/generate -d <span class="hljs-string">'{
  "model": "llama3",
  "prompt":"What is 1+1?"
 }'</span>
</code></pre>
<p>Next, install <a target="_blank" href="https://github.com/Jonathan-Adly/AgentRun">AgentRun</a> and have its REST API running. You will need <a target="_blank" href="https://www.docker.com/products/docker-desktop/">docker</a> installed to use docker-compose.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/Jonathan-Adly/agentrun
<span class="hljs-built_in">cd</span> agentrun/agentrun-api
cp .env.example .env.dev
docker-compose up -d --build
</code></pre>
<p>And again, let's make a test request to make sure everything is running correctly.</p>
<pre><code class="lang-bash">curl -X GET http://localhost:8000/v1/health/
<span class="hljs-comment"># {"status":"ok"}</span>
</code></pre>
<p>Next, we will run a Python script that will be our starting point to run queries against Llama3 with Agentrun.</p>
<pre><code class="lang-bash">
python -m venv agentrun-venv
<span class="hljs-comment"># windows: .\agentrun-venv\Scripts\activate</span>
<span class="hljs-built_in">source</span> agentrun-venv/bin/activate
<span class="hljs-comment"># windows: New-Item main.py -type file</span>
touch main.py

pip install requests json_repair
</code></pre>
<p>In the file, we will start off by importing the necessary libraries. We'll need <code>json</code> for handling data and <code>requests</code> for making HTTP calls. We’re also using a cool library called <code>json_repair</code> just in case our JSON data decides to act up and we need to fix it on the fly. This is especially the case if use 8B version of Llama3 where the JSON sometimes is slightly broken.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> json_repair
<span class="hljs-keyword">import</span> requests
</code></pre>
<h3 id="heading-step-2-define-the-function-amp-tools">Step 2: Define the Function &amp; Tools</h3>
<p>We've crafted a simple function <code>execute_python_code</code>. This function is pretty straightforward—it sends a Python code snippet to a code execution environment provided by AgentRun and fetches the output.</p>
<p>Here's a quick peek at how this works:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">execute_python_code</span>(<span class="hljs-params">code: str</span>) -&gt; str:</span>
    code = json.dumps({<span class="hljs-string">"code"</span>: code})
    response = requests.post(
        <span class="hljs-string">"http://localhost:8000/v1/run/"</span>,
        data=code,
        headers={<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>},
    )
    print(code)    
    output = response.json()[<span class="hljs-string">"output"</span>]
    <span class="hljs-keyword">return</span> output
</code></pre>
<p>We basically format the code snippet into JSON, send it off to our localhost where the magic happens, and get back the result. You can read more about how AgentRun works <a target="_blank" href="https://jonathan-adly.github.io/AgentRun/">here</a>.</p>
<p>Next, we would use this function as our basis for defining the tool that we want Llama3 to use. Here is what this looks like.</p>
<pre><code class="lang-python">tools = [
    {
        <span class="hljs-string">"type"</span>: <span class="hljs-string">"function"</span>,
        <span class="hljs-string">"function"</span>: {
            <span class="hljs-string">"name"</span>: <span class="hljs-string">"execute_python_code"</span>,
            <span class="hljs-string">"description"</span>: <span class="hljs-string">"""Sends a python code snippet to the code execution environment and returns the output. 
            The code execution environment can automatically import any library or package by importing. 
            The code snippet to execute must be a valid python code and must use print() to output the result."""</span>,
            <span class="hljs-string">"parameters"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>,
                <span class="hljs-string">"properties"</span>: {
                    <span class="hljs-string">"code"</span>: {
                        <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>,
                        <span class="hljs-string">"description"</span>: <span class="hljs-string">"The code snippet to execute. Must be a valid python code. Must use print() to output the result."</span>,
                    },
                },
                <span class="hljs-string">"required"</span>: [<span class="hljs-string">"code"</span>],
            },
        },
    },
]
</code></pre>
<p>Lastly, we will set up our model here. We can use the base Llama3 or any of the finetunes provided by the community. For the sake of experimentations, I ran my experiment using Dolphin-llama3 8b finetune.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Ollama dolphin-llama3 page: https://ollama.com/library/dolphin-llama3</span>
MODEL = <span class="hljs-string">"dolphin-llama3"</span>
</code></pre>
<h3 id="heading-step-3-the-integration-with-ollama-and-llama3">Step 3: The Integration with Ollama and Llama3</h3>
<p>Moving on to the cooler element—integration with the Ollama and Llama3.</p>
<p>Here’s a how the query processing and tool selection works:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_full_completion</span>(<span class="hljs-params">prompt: str, model: str = MODEL</span>) -&gt; dict[str, str]:</span>
    <span class="hljs-comment"># setting up the parameters including our model</span>
    params = {
        <span class="hljs-string">"model"</span>: model,
        <span class="hljs-string">"prompt"</span>: prompt,
        <span class="hljs-string">"stream"</span>: <span class="hljs-literal">False</span>,
        <span class="hljs-comment"># seed and temperature for deterministic output</span>
        <span class="hljs-string">"temperature"</span>: <span class="hljs-number">0</span>,
        <span class="hljs-string">"seed"</span>: <span class="hljs-number">123</span>,
        <span class="hljs-comment"># format is JSON, since we are interested in tools/function calling</span>
        <span class="hljs-string">"format"</span>: <span class="hljs-string">"json"</span>,
    }
    <span class="hljs-comment"># making the post request and handling responses</span>
    <span class="hljs-keyword">try</span>:
        response = requests.post(
            <span class="hljs-string">f"http://localhost:11434/api/generate"</span>,
            headers={<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>},
            data=json.dumps(params),
            timeout=<span class="hljs-number">60</span>,
        )
        <span class="hljs-keyword">return</span> json_repair.loads(response.text)
    <span class="hljs-keyword">except</span> requests.RequestException <span class="hljs-keyword">as</span> err:
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"error"</span>: <span class="hljs-string">f"API call error: <span class="hljs-subst">{str(err)}</span>"</span>}
</code></pre>
<h3 id="heading-step-4-putting-it-all-together">Step 4: Putting It All Together</h3>
<p>Now, that we have everything setup. We will simply use a prompt to nudge the model toward using our <code>execute_python_code</code> tool for its outputs.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_answer</span>(<span class="hljs-params">query: str</span>) -&gt; str:</span>
    functions_prompt = <span class="hljs-string">f"""
        You have access to the following tools:
            <span class="hljs-subst">{tools}</span>
        You must follow these instructions:
        If a user query requires a tool, you must select the appropriate tool from the list of tools provided.
        Always select one or more of the above tools based on the user query
        If a tool is found, you must respond in the JSON format matching the following schema:
        {{
        "tools": {{
            "tool": "&lt;name of the selected tool&gt;",
            "tool_input": &lt;parameters for the selected tool, matching the tool's JSON schema
        }}
        }}
        If there are multiple tools required, make sure a list of tools are returned in a JSON array.
        If there is no tool that match the user request, you will respond with empty json.
        Do not add any additional Notes or Explanations.

        User Query: <span class="hljs-subst">{query}</span>
        """</span>

    r_dict = generate_full_completion(functions_prompt)
    r_tools = json_repair.loads(r_dict[<span class="hljs-string">"response"</span>])[<span class="hljs-string">"tools"</span>]
    code = r_tools[<span class="hljs-string">"tool_input"</span>][<span class="hljs-string">"code"</span>]
    response = execute_python_code(code)
    <span class="hljs-keyword">return</span> response
</code></pre>
<p>Finally, when you feed it a query like "what's the 12312 * 321?" the whole system whirls into action, the model figures out which tool and code snippet to use, executes it, and bam! You've got your answer.</p>
<h3 id="heading-just-to-show-off">Just to Show Off</h3>
<p>Let’s see it in action with a couple of examples:</p>
<pre><code class="lang-python"><span class="hljs-comment"># 3952152</span>
print(get_answer(<span class="hljs-string">"what's 12312 *321?"</span>))
<span class="hljs-comment"># 500</span>
print(get_answer(<span class="hljs-string">"how many even numbers are there between 1 and 1000?"</span>))
<span class="hljs-comment"># Paris</span>
print(get_answer(<span class="hljs-string">"what's the capital of France?"</span>))
</code></pre>
<p>We're blending advanced model integration with practical code execution. Whether you're automating tasks, building out a project, or just playing around to see the capabilities, this setup might just be your next go-to.</p>
<p>And, there you go—a delightful mix of Python, APIs, and some AI magic to streamline how you handle and execute code snippets. As always, tweak, tinker, and tailor it to your needs. Happy coding, everyone!</p>
]]></content:encoded></item><item><title><![CDATA[Open Sourcing a Python Project the Right Way in 2024]]></title><description><![CDATA[Every Python developer I've talked to has written some code that others would find useful. At the same time, they've all spent days, if not longer, wrestling with the tooling and packaging that comes with the language. My aim with this article is to ...]]></description><link>https://jonathanadly.com/open-sourcing-a-python-project-the-right-way-in-2024</link><guid isPermaLink="true">https://jonathanadly.com/open-sourcing-a-python-project-the-right-way-in-2024</guid><category><![CDATA[Python]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Thu, 18 Apr 2024 02:19:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713361060275/a8e44431-c623-400f-a3b8-b33d330e81ee.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every Python developer I've talked to has written some code that others would find useful. At the same time, they've all spent days, if not longer, wrestling with the tooling and packaging that comes with the language. My aim with this article is to simplify the process of open-sourcing your Python code as much as possible. By the time you finish reading, you'll know how to take your existing code base and turn it into an open source project that's easy to use and contribute to.</p>
<p>This was inspired by:</p>
<ul>
<li><p>Trying to open-source my package <a target="_blank" href="https://github.com/Jonathan-Adly/AgentRun">AgentRun</a> and struggling for a few days with Python tooling</p>
</li>
<li><p>Jeff Knupp excellent article on <a target="_blank" href="https://jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way//">how to open source a Python project</a> hitting the 10+ year mark and needing an update</p>
</li>
<li><p>Simon Willison updated cookiecutter tool <a target="_blank" href="https://github.com/simonw/python-lib">python-lib</a> that did most of the heavy lifting with the pain of automating the publishing process to PyPI.</p>
</li>
</ul>
<h1 id="heading-tools-and-concepts">Tools and Concepts</h1>
<p>When you're gearing up to open source a Python project, there are a handful of tools and concepts that really come in handy. I'm going to walk you through some of the essentials that I've found invaluable. Keep in mind, my recommendations are based on personal experience, so they might be a bit subjective.</p>
<p>Let's break it down:</p>
<ul>
<li><p><strong>Project Layout</strong>: How to structure your files.</p>
</li>
<li><p><strong>The pyproject.toml File</strong>: This is crucial for project settings.</p>
</li>
<li><p><strong>Pytest</strong>: For all your testing needs.</p>
</li>
<li><p><strong>GitHub Actions</strong>: Automate workflows directly from your GitHub repository.</p>
</li>
<li><p><strong>MkDocs</strong>: For awesome documentation.</p>
</li>
<li><p><strong>PyPI Trusted Publishing</strong>: Get your package out there easily.</p>
</li>
<li><p><strong>Cookiecutter</strong>: A lifesaver for starting projects quickly.</p>
</li>
<li><p><strong>Recipe</strong>: Step-by-step guide to get you rolling.</p>
</li>
</ul>
<p>If you're already familiar with these tools, feel free to jump straight to the Recipe section where you can follow the practical steps to get your project live.</p>
<h1 id="heading-project-layout">Project Layout</h1>
<p>When you're open sourcing a Python project, the way you organize your project layout is crucial. It's often the first thing potential contributors or users notice. A cluttered or confusing structure can be overwhelming for newcomers, so it's essential to get it right from the start.</p>
<p>Every project should include at least three key directories:</p>
<ol>
<li><p>A <code>docs</code> directory for all your project documentation.</p>
</li>
<li><p>A directory named after your project where the actual Python package lives.</p>
</li>
<li><p>A <code>tests</code> directory to hold all your test files.</p>
</li>
</ol>
<p>On top of these, you'll typically have several important top-level files like <code>LICENSE</code>, <a target="_blank" href="http://README.md"><code>README.md</code></a>, and possibly a few others. However, it's wise to keep the number of top-level files to a minimum. To give you a clearer picture, here's a simplified snapshot of the layout for one of my projects, <a target="_blank" href="https://github.com/Jonathan-Adly/AgentRun">AgentRun</a>.</p>
<pre><code class="lang-plaintext">├── .github
│   └── workflows
│       ├── publish.yml
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── agentrun
│   └── __init__.py
├── agentrun-api
├── docs
├── mkdocs.yml
├── pyproject.toml
└── tests
    └── test_agentrun.py
</code></pre>
<h1 id="heading-the-pyprojecttoml-file">The <code>pyproject.toml</code> File</h1>
<p>The <code>pyproject.toml</code> file is a configuration file for Python projects, standardized by <a target="_blank" href="https://peps.python.org/pep-0518/">PEP 518.</a> It specifies build system requirements and can be used to configure tools like <code>black</code>, <code>isort</code>, and <code>pytest</code> and many others. This file is crucial for modern Python packaging and dependency management.</p>
<p>In the context of open-sourcing a Python package, <code>pyproject.toml</code> plays several roles:</p>
<ol>
<li><p><strong>Dependency Specification</strong>: It declares build dependencies required to compile your package from source. This ensures that anyone trying to build your package from the source will have the right tools installed automatically.</p>
</li>
<li><p><strong>Package Metadata</strong>: It can include metadata about your package such as name, version, authors, and more. This information is essential for package distribution and maintenance.</p>
</li>
<li><p><strong>Tool Configuration</strong>: It allows you to configure various tools used during development in a single, standardized file. For example, settings for formatters, linters, and test frameworks can be specified here, ensuring consistency across environments.</p>
</li>
<li><p><strong>Build System Declaration</strong>: It declares which build backend (like <code>setuptools</code>, <code>flit</code>, or <code>poetry</code>) is used to build your package. This is crucial for reproducibility and compatibility in different environments.</p>
</li>
</ol>
<p>The difficult thing about this part is that you have many options and configurations you can choose from. In the beginning I recommend to keep it simple and gradually add more tools as you are get more comfortable.</p>
<p>Here is a minimal <code>pyproject.toml</code> similar to the one generated by <a target="_blank" href="https://github.com/simonw/python-lib">python-lib</a> that I recommend to start with.</p>
<pre><code class="lang-ini"><span class="hljs-section">[project]</span>
<span class="hljs-attr">name</span> = <span class="hljs-string">"Your project name"</span>
<span class="hljs-attr">version</span> = <span class="hljs-string">"0.1"</span>
<span class="hljs-attr">description</span> = <span class="hljs-string">"Your project description"</span>
<span class="hljs-attr">readme</span> = <span class="hljs-string">"README.md"</span>
<span class="hljs-attr">requires-python</span> = <span class="hljs-string">"&gt;=3.8"</span>
<span class="hljs-attr">authors</span> = [{name = <span class="hljs-string">"your name"</span>}]
<span class="hljs-attr">license</span> = {text = <span class="hljs-string">"the license choosen from the project"</span>}
<span class="hljs-attr">classifiers</span> = [
    <span class="hljs-string">"License :: OSI Approved :: {{ the license name }} "</span>
]
<span class="hljs-attr">dependencies</span> = []

<span class="hljs-section">[build-system]</span>
<span class="hljs-attr">requires</span> = [<span class="hljs-string">"setuptools"</span>]
<span class="hljs-attr">build-backend</span> = <span class="hljs-string">"setuptools.build_meta"</span>

<span class="hljs-section">[project.urls]</span>
<span class="hljs-attr">Homepage</span> = <span class="hljs-string">"your github repo page"</span>

<span class="hljs-section">[project.optional-dependencies]</span>
<span class="hljs-attr">test</span> = [<span class="hljs-string">"pytest"</span>, <span class="hljs-string">"mkdocs"</span>]
</code></pre>
<p>This <code>pyproject.toml</code> file doesn't have any dependencies and only <code>pytest</code> and <code>mkdocs</code> for the test version of the package. So, this is just a starting point and would need to be adjusted depending on your own project needs.</p>
<h1 id="heading-pytest">Pytest</h1>
<p>When publishing an open-source package, it's good to include some tests. This not only invites contributors to your project but also reassures users about the reliability of your package. Without tests, there's a risk that contributors might unintentionally break existing features, and potential users might hesitate to depend on your software.</p>
<p><a target="_blank" href="https://docs.pytest.org/en/8.0.x/">Pytest</a> is one of the most popular Python testing framework and I recommend starting from the beginning with it.</p>
<p>Let's imagine you have a package that simply increments a number by 1. Here is the code in your <code>package/__init__.py</code> :</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">inc</span>(<span class="hljs-params">x</span>):</span>
    <span class="hljs-keyword">return</span> x + <span class="hljs-number">1</span>
</code></pre>
<p>Then under your <code>tests/</code> directory. You should have a file <code>test_inc.py</code> that uses Pytest to test your code.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> package <span class="hljs-keyword">import</span> inc

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_inc</span>():</span>
    <span class="hljs-keyword">assert</span> inc(<span class="hljs-number">3</span>) == <span class="hljs-number">4</span>
</code></pre>
<p>To run your tests, simply type <code>pytest</code> in the terminal. Pytest will execute all your tests and report back whether they passed or failed. This immediate feedback loop is invaluable for maintaining a robust codebase.</p>
<h1 id="heading-github-actions">Github Actions</h1>
<p>When you're planning to share and collaborate on a project, having a solid CI/CD setup is important. Continuous integration (CI) refers to the practice of automatically integrating and testing code changes into a shared source code repository without breaking anything. Continuous Delivery, automates the release of validated code to a repository following the tests that happen in CI.</p>
<p>At a minimum, you want your code tested automatically every time someone pushes a new change. There are many vendors and tools that can do that, but I like Github Actions. Github is by far the most popular platform to store and share code and having native CI/CD in the same place as your code makes things easier.</p>
<p>You store all your Github actions in a directory called <code>.github/workflows</code> . Here is a simple github action that tests your code every time someone's open a Pull Request or pushes code.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Test</span>

<span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>, <span class="hljs-string">pull_request</span>]

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">matrix:</span>
        <span class="hljs-attr">python-version:</span> [<span class="hljs-string">"3.10"</span>]
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.python-version</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v5</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">python-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.python-version</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">cache:</span> <span class="hljs-string">pip</span>
        <span class="hljs-attr">cache-dependency-path:</span> <span class="hljs-string">pyproject.toml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pip install '.[test]'
</span>    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
        <span class="hljs-string">pytest</span>
</code></pre>
<h1 id="heading-mkdocs">Mkdocs</h1>
<p>MkDocs is a fast, and simple static site generator that's geared towards building project documentation. Documentation source files are written in Markdown, and configured with a single YAML configuration file - <code>mkdocs.yml</code>.</p>
<p>There are many plugins, themes, and automation recipes that helps with documenting your package. But to start, you really need only 2 things.</p>
<ol>
<li><p><code>mkdocs.yml</code> configuration file</p>
</li>
<li><p><code>index.md</code> file under the <code>docs/</code> directory</p>
</li>
</ol>
<p>The <code>mkdocs.yml</code> only needs a site name and a site url to be valid. Here is a minimal example:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">site_name:</span> <span class="hljs-string">My</span> <span class="hljs-string">Documentation</span>
<span class="hljs-attr">site_url:</span> <span class="hljs-string">https://example.com</span>
</code></pre>
<h1 id="heading-pypi-trusted-publishing">PyPI Trusted Publishing</h1>
<p>Your code should now be ready for you to build and distribute to PyPI. The process is quite simple with PyPI Trusted publishing mechanism. You need to sign into PyPI and create a new “pending publisher”. Here is what that looks like.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713396178474/d648551e-f4f3-40bf-9cea-722518866853.png" alt class="image--center mx-auto" /></p>
<p>Trusted publishing is essentially you allowing your GitHub repository <code>your-name/your-package</code> to publish packages with the name <code>your-package</code> via a github action.</p>
<p>The next step is to create a <code>publish.yml</code> in your <code>.github/workflows</code> that uses Github Action to publish your code to PyPI automatically whenever you create a release.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Publish</span> <span class="hljs-string">Python</span> <span class="hljs-string">Package</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">release:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">matrix:</span>
        <span class="hljs-attr">python-version:</span> [<span class="hljs-string">"3.10"</span>]
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.python-version</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v5</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">python-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.python-version</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">cache:</span> <span class="hljs-string">pip</span>
        <span class="hljs-attr">cache-dependency-path:</span> <span class="hljs-string">pyproject.toml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pip install '.[test]'
</span>    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pytest
</span>  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> [<span class="hljs-string">test</span>]
    <span class="hljs-attr">environment:</span> <span class="hljs-string">release</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v5</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">python-version:</span> <span class="hljs-string">"3.12"</span>
        <span class="hljs-attr">cache:</span> <span class="hljs-string">pip</span>
        <span class="hljs-attr">cache-dependency-path:</span> <span class="hljs-string">pyproject.toml</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pip install setuptools wheel build
</span>    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        python -m build
</span>    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Publish</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">pypa/gh-action-pypi-publish@release/v1</span>
</code></pre>
<p>Let's simplify this file and see what's happening:</p>
<ul>
<li><p>Essentially, we have two main tasks here: testing and deploying.</p>
</li>
<li><p>In the deployment section, we're tackling a couple of key activities. First off, we install the necessary tools for packaging—these include <code>setuptools</code>, <code>wheel</code>, and <code>build</code>. Next, we use Python’s build module to actually build the package. Finally, we publish the package to PyPI using the <code>pypa/gh-action-pypi-publish@release/v1</code> action.</p>
</li>
</ul>
<p>So, in a nutshell, we're testing the code, building it, and then pushing it up to PyPI.</p>
<h1 id="heading-cookiecutter">Cookiecutter</h1>
<p>Cookiecutter is a command line tool that automates the process of starting a project. Instead of going through all these steps manually, you can just run cookiecutter and the tool will generate all the necessary files and directory structure.</p>
<p>For tools discussed, I tried to follow <a target="_blank" href="https://github.com/simonw/python-lib">python-lib</a> structure and recommendations (only adding <code>mkdocs</code> as an extra dependency). So, to achieve the same results you can:</p>
<ol>
<li><p>Install cookiecutter - <code>pipx install cookiecutter</code></p>
</li>
<li><p>Run it - <code>cookiecutter gh:simonw/python-lib</code></p>
</li>
<li><p>Optional: Add <code>mkdocs</code> as discussed above.</p>
</li>
</ol>
<p>There are many cookiecutter templates around with all kind of different options. The truth is, you really want a minimal well-maintained one.</p>
<p>Cookiecutter is great as a starting point, but relying completely on it without understanding all the tools inside can be painful when things go wrong. That's why I recommend python-lib which gives you the minimum tools you need to publish your package and nothing else.</p>
<h1 id="heading-recipe">Recipe</h1>
<ol>
<li><p>Start by creating a PyPI account if you don't already have one. Check to ensure the package name you want isn’t already taken.</p>
</li>
<li><p>Next, set up a new GitHub repository with your package name.</p>
</li>
<li><p>Install Cookiecutter if you haven't already</p>
<p> <code>pipx install cookiecutter</code></p>
</li>
<li><p>Run Cookiecutter and answer the prompts to generate your project skeleton</p>
<p> <code>cookiecutter gh:simonw/python-lib</code></p>
</li>
<li><p>Create a virtual environment in your project directory and activate it.</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">cd</span> my-new-library
 <span class="hljs-comment"># Create and activate a virtual environment:</span>
 python3 -mvenv venv
 <span class="hljs-built_in">source</span> venv/bin/activate
 <span class="hljs-comment"># Install dependencies so you can edit the project:</span>
 pip install -e <span class="hljs-string">'.[test]'</span>
 <span class="hljs-comment"># With zsh you have to run this again for some reason:</span>
 <span class="hljs-built_in">source</span> venv/bin/activate
 <span class="hljs-comment"># test the example function</span>
 pytest
</code></pre>
</li>
<li><p>Initialize a Git repository, commit the initial project structure, and push it to GitHub</p>
<pre><code class="lang-bash"> git init
 git add -A
 git commit -m <span class="hljs-string">"Initial structure from template"</span>
 git remote add origin https://github.com/{{github_name}}/{{repo_name}}.git
 git push -u origin main
</code></pre>
</li>
<li><p>Check the GitHub <strong>Actions</strong> tab in your repository to confirm that the test workflow is running. This setup ensures your code is tested with every push.</p>
</li>
<li><p>On your local machine, create a 'develop' branch for ongoing development.</p>
<pre><code class="lang-bash"> git checkout -b <span class="hljs-string">"develop"</span>
</code></pre>
</li>
<li><p>Add <code>MkDocs</code> to your development dependencies in the <code>pyproject.toml</code> file.</p>
<pre><code class="lang-yaml"> <span class="hljs-comment"># nothing else changed</span>
 [<span class="hljs-string">project.optional-dependencies</span>]
 <span class="hljs-string">test</span> <span class="hljs-string">=</span> [<span class="hljs-string">"pytest"</span>, <span class="hljs-string">"mkdocs"</span>]
</code></pre>
</li>
<li><p>Create a docs directory and add <code>index.md</code> in there. You can copy the info in the <code>README.md</code> to <code>index.md</code> for now.</p>
</li>
<li><p>Create a <code>mkdocs.yml</code> file. Here is a minimal example.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">site_name:</span> <span class="hljs-string">&lt;your_package&gt;</span> <span class="hljs-string">documentation</span>
<span class="hljs-attr">site_url:</span> <span class="hljs-string">https://&lt;your_username&gt;.github.io/&lt;repo_name&gt;-docs/</span>
</code></pre>
</li>
<li><p>Install mkdocs and deploy a documentation site.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># update the dependencies</span>
pip install -e <span class="hljs-string">'.[test]'</span>
<span class="hljs-comment"># test and adjust the documentations as needed</span>
mkdocs serve
<span class="hljs-comment"># deploy your documentations to github pages</span>
mkdocs gh-deploy
<span class="hljs-comment"># Add site/ to your .gitignore file</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"site/"</span> &gt;&gt; .gitignore
</code></pre>
</li>
<li><p>When you are happy with the changes, commit your documentation changes to the 'develop' branch and push</p>
<pre><code class="lang-bash">git add -A
git commit -m <span class="hljs-string">"documentation"</span>
git push -u origin develop
</code></pre>
</li>
<li><p>For new features, create a feature branch, add your code, and then merge it back into 'develop' when ready</p>
<pre><code class="lang-bash">git checkout -b <span class="hljs-string">"new-feature"</span>
<span class="hljs-comment"># work on your code until done</span>
git add -A
git commit -m <span class="hljs-string">"feature complete"</span>
git checkout develop
<span class="hljs-comment"># now we merge the completed feature</span>
git merge <span class="hljs-string">"new-feature"</span>
</code></pre>
</li>
<li><p>When ready to release, merge 'develop' into 'main', pushing the changes to GitHub</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Don't forget to upgrade your version in pyproject.toml every time you merge develop into main after the initial release.</div>
</div>

<pre><code class="lang-bash">git checkout main
git merge develop
git push
</code></pre>
</li>
<li><p>On Github - create a new release for your package:</p>
<details><summary>On your repository’s main page, click on "Releases" which is typically found on the right side of the sub-navigation menu under the code tab.</summary><div data-type="detailsContent">Click the “Draft a new release” button or the “Create a new release” link if you don't have any releases yet and fill the information there.</div></details></li>
<li>On release creation, the publish Github action would run and automatically publish your releases to PyPI</li>
</ol>
<p>And that's it. Congratulations, you have now published your first open-source Python package with all what you need for future contributors and users.</p>
<p>For a practical example, check out the <a target="_blank" href="https://github.com/Jonathan-Adly/AgentRun">AgentRun</a> repository, which uses the same tools and processes outlined here.</p>
]]></content:encoded></item><item><title><![CDATA[Using GPT-4 Over Email]]></title><description><![CDATA[I recently came across a tweet from a founder in my network, who had an interesting question on Twitter: "Why can't you email an assistant and just a get a response without any hassle?" Around the same time, I noticed a discussion on r/residency abou...]]></description><link>https://jonathanadly.com/using-gpt-4-over-email</link><guid isPermaLink="true">https://jonathanadly.com/using-gpt-4-over-email</guid><category><![CDATA[bitcoin payments]]></category><category><![CDATA[GPT 4]]></category><category><![CDATA[webhooks]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Mon, 15 Apr 2024 19:33:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713150202252/9b0e8c93-32fb-4b16-8e01-df858d443734.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently came across a tweet from a founder in my network, who had an interesting question on Twitter: "<a target="_blank" href="https://twitter.com/_abi_/status/1757801527811404194">Why can't you email an assistant and just a get a response without any hassle?</a>" Around the same time, I noticed a <a target="_blank" href="https://www.reddit.com/r/Residency/comments/16bowgt/why_is_chatgpt_blocked_off_every_hospitals/">discussion</a> on r/residency about GPT-4 being blocked across various hospital networks.</p>
<p>A few days later, I discovered that GPT-4 access is restricted in several countries, and private usage is quite limited. This prompted me to revisit some old code I had, which was designed to handle email webhooks for incoming messages. I decided to adapt this code to create a solution for these issues because it would be cool.</p>
<p>Thus, Assistant-OverMail was born—an open-source project aimed at simplifying access to GPT-4 via email.</p>
<h1 id="heading-assistant-overmail"><strong>Assistant-OverMail</strong></h1>
<p><a target="_blank" href="https://github.com/Jonathan-Adly/assistant-overmail/tree/main">Assistant-OverMail</a> is an open-source web application designed to simplify your workflow by emailing <strong>assistant@overmail.a</strong>i (or any domain name if you self-host) and getting a response back from GPT-4.</p>
<p>You can use this to use GPT-4 privately, go around employers' blocks, or simply as a short-cut to deal with emails in the inbox. You can try it at: <a target="_blank" href="http://overmail.ai">overmail.ai</a></p>
<p>For those interested in self-hosting, the setup requires Docker, Docker Compose, and a standard Nginx + SSL configuration. Assistant-OverMail is compatible with any email service provider that supports SMTP, such as Mailgun, SendGrid, or AWS SES. Just provide your SMTP credentials in the <code>.env</code> file to get started.</p>
<h1 id="heading-how-does-it-work">How does it work?</h1>
<p>The main business logic is really around emails webhooks. All mail providers as far as I know provide a way to send you webhooks on receiving emails that match certain rules. Here is an example from my mailgun dashboard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713148768780/fd9dc4d4-ffd9-49e3-959d-eface82ad0ba.png" alt class="image--center mx-auto" /></p>
<p>In our case, whenever an email is sent to "<a target="_blank" href="mailto:assistant@overmail.ai">assistant@overmail.ai</a>", mailgun forwards this email as a form POST to a designated endpoint. This endpoint performs several checks before passing the email content to GPT-4 for processing. After generating a response, the system utilizes Django's built-in email capabilities to send this response back to the sender.</p>
<h1 id="heading-parsing-email-addresses">Parsing Email Addresses</h1>
<p>One new thing I learned from trying to improve my old code is Python's <code>parseaddr.</code> My old code had a big block of code handling all kind of different emails formatting on the FROM field with <code>if</code> statements.</p>
<p>The <code>parseaddr</code> function from Python's <code>email.utils</code> module is used to parse a string into its constituent name and email address parts. It takes a single string argument, which typically represents an email address with an optional display name, and returns a tuple of the form <code>(realname, email_address)</code>. Here's a quick breakdown:</p>
<ol>
<li><p><strong>Input</strong>: A string in the format <code>"Display Name &lt;</code><a target="_blank" href="mailto:email@example.com"><code>email@example.com</code></a><code>&gt;"</code> or just <code>"</code><a target="_blank" href="mailto:email@example.com"><code>email@example.com</code></a><code>"</code>.</p>
</li>
<li><p><strong>Output</strong>: A tuple where the first element is the display name (if provided, otherwise an empty string), and the second element is the email address.</p>
</li>
</ol>
<p>The function is quite handy for extracting email addresses and names from headers or similar formats in emails where such data may be presented in a more human-readable form.</p>
<h1 id="heading-payments">Payments</h1>
<p>Stripe checkouts are relatively straightforward. You generate a URL on the server from your Stripe key and the Price id, and redirect your user to that URL. They pay on Stripe infrastructure, and you get notified via a webhook on success.</p>
<p>I did want to experiment with Bitcoin payments though as something new to learn. This feature, ended up taking almost a week - mostly because I didn't know what I was doing (but now I do). It really deserves its own post, but at a glance - there are three ways you can accept Bitcoin payments over the internet, ranked by effort:</p>
<ol>
<li><p>Via BTCPay server on your own infrastructure</p>
</li>
<li><p>Custodial services APIs (Alby or Strike for example).</p>
</li>
<li><p>Via a payment processor on their infrastructure</p>
</li>
</ol>
<p>Since this is a small project that I wasn't looking forward to maintain a server for or build a custom checkout experience on top of Alby or Strike APIs- I ended up going with #3.</p>
<p>OpenNode is a Stripe-like service that essentially offers the same type of workflow for Bitcoin. A checkout URL is generated on the server, and the user is redirected there. They pay on OpenNode infrastructure, and you receive a webhook on success.</p>
<h1 id="heading-frontend">Frontend</h1>
<p>This is a quite simple project, and so I decided to go with a Carrd template for it. All what was needed is really a form to submit an email address too, and everything else is either payment on someone's else infrastructure or a webhook.</p>
<h1 id="heading-alternatives">Alternatives</h1>
<p>There are a few Zaps from Zapier that offers Email to GPT-4 integration. There is also at least 1 extension that works in the browser to autofill email responses.</p>
<p>I consider this project complete and overall a success. You can find the completed project here: <a target="_blank" href="https://overmail.ai">https://overmail.ai</a> and the code - MIT licensed here: <a target="_blank" href="https://github.com/Jonathan-Adly/assistant-overmail/tree/main">https://github.com/Jonathan-Adly/assistant-overmail/</a></p>
]]></content:encoded></item><item><title><![CDATA[How to start a Python project in 2024]]></title><description><![CDATA[When I begin a new Python project, one of the first steps I take is to create a virtual environment. This is crucial for managing dependencies and ensuring that the libraries used in my project do not conflict with those of other projects or the syst...]]></description><link>https://jonathanadly.com/how-to-start-a-python-project-in-2024</link><guid isPermaLink="true">https://jonathanadly.com/how-to-start-a-python-project-in-2024</guid><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jonathan Adly]]></dc:creator><pubDate>Mon, 15 Apr 2024 01:53:56 GMT</pubDate><content:encoded><![CDATA[<p>When I begin a new Python project, one of the first steps I take is to create a virtual environment. This is crucial for managing dependencies and ensuring that the libraries used in my project do not conflict with those of other projects or the system itself. Here’s how I set up a Python project using a virtual environment.</p>
<h4 id="heading-step-1-create-a-new-project-directory">Step 1: Create a New Project Directory</h4>
<p>I start by creating a new directory for my project and navigate into it:</p>
<pre><code class="lang-bash">mkdir my_project
<span class="hljs-built_in">cd</span> my_project
</code></pre>
<h4 id="heading-step-2-initialize-a-git-repository">Step 2: Initialize a Git Repository</h4>
<p>Next, I initialize a git repository to manage version control:</p>
<pre><code class="lang-bash">git init
</code></pre>
<h4 id="heading-step-3-create-the-virtual-environment">Step 3: Create the Virtual Environment</h4>
<p>I use the <code>venv</code> module to create a virtual environment in my project directory, naming it after my project followed by <code>-venv</code> to maintain clarity:</p>
<pre><code class="lang-bash">python -m venv myproject-venv
</code></pre>
<p>This command creates a directory named <code>myproject-venv</code> where the virtual environment files are stored.</p>
<h4 id="heading-step-4-activate-the-virtual-environment">Step 4: Activate the Virtual Environment</h4>
<p>Before installing any packages, I activate the virtual environment. The method differs slightly depending on the operating system:</p>
<ul>
<li><p><strong>On macOS and Linux:</strong></p>
<pre><code class="lang-bash">  <span class="hljs-built_in">source</span> myproject-venv/bin/activate
</code></pre>
</li>
</ul>
<ul>
<li><p><strong>On Windows:</strong></p>
<pre><code class="lang-bash">  .\myproject-venv\Scripts\activate
</code></pre>
</li>
</ul>
<p>The prompt in the shell changes to show the name of the environment, indicating that it is now active.</p>
<h4 id="heading-step-5-install-required-packages">Step 5: Install Required Packages</h4>
<p>With the virtual environment activated, I install the necessary packages using <code>pip</code>:</p>
<pre><code class="lang-bash">pip install &lt;package_name&gt;
</code></pre>
<p>For instance, to install Flask:</p>
<pre><code class="lang-bash">pip install flask
</code></pre>
<h4 id="heading-step-6-save-dependencies">Step 6: Save Dependencies</h4>
<p>It’s good practice to keep track of the project's dependencies. I do this by creating a <code>requirements.txt</code> file:</p>
<pre><code class="lang-bash">pip freeze &gt; requirements.txt
</code></pre>
<p>This file is crucial for replicating the environment on other machines or by other developers working on the project.</p>
<h4 id="heading-step-7-start-coding">Step 7: Start Coding</h4>
<p>Now, I can start coding my project. I create Python scripts in the project directory and run them using the Python interpreter that is part of my virtual environment.</p>
<h4 id="heading-step-8-deactivate-the-virtual-environment">Step 8: Deactivate the Virtual Environment</h4>
<p>When I'm done working, I deactivate the virtual environment by running:</p>
<pre><code class="lang-bash">deactivate
</code></pre>
<p>This workflow helps me maintain a clean and organized working environment and makes it easier to manage project-specific dependencies. By following these steps, I ensure that each of my Python projects is set up correctly and ready for development.</p>
]]></content:encoded></item></channel></rss>