<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: amier_san09</title>
    <description>The latest articles on DEV Community by amier_san09 (@mhmmdamier).</description>
    <link>https://dev.to/mhmmdamier</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F750375%2F82fdae69-7d03-47c8-8544-aedc9c0597e9.jpeg</url>
      <title>DEV Community: amier_san09</title>
      <link>https://dev.to/mhmmdamier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mhmmdamier"/>
    <language>en</language>
    <item>
      <title>AI-Alchemist: Turn Open Models Into Intelligent Creations</title>
      <dc:creator>amier_san09</dc:creator>
      <pubDate>Tue, 27 Oct 2026 22:36:39 +0000</pubDate>
      <link>https://dev.to/mhmmdamier/ai-alchemist-turn-open-models-into-intelligent-creations-25c4</link>
      <guid>https://dev.to/mhmmdamier/ai-alchemist-turn-open-models-into-intelligent-creations-25c4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gabutersproject/ai-alchemist" rel="noopener noreferrer"&gt;AI Alchemist&lt;/a&gt; is a local laboratory for open-weight models. The tagline is "Turn Open Models Into Intelligent Creations." Version 0.2.0. The command is &lt;code&gt;ai-alchemist&lt;/code&gt;. The Python package is &lt;code&gt;ai_alchemist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I built it because trying a local model usually means a terminal, a host URL, and a guess about whether the reply came from the model I thought I selected. I wanted one page where the model name, the provider, and the reply stay visible, and where two models can answer the same prompt without one of them being declared the winner.&lt;/p&gt;

&lt;p&gt;The model runs through &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. The browser does not call Ollama itself. A small server on the machine does. The default host is &lt;code&gt;http://127.0.0.1:11434&lt;/code&gt;. If that address is loopback (&lt;code&gt;127.0.0.1&lt;/code&gt;, &lt;code&gt;localhost&lt;/code&gt;, or &lt;code&gt;::1&lt;/code&gt;), the interface says the inference is local. If I point the host anywhere else, the interface says the inference is remote and that the prompt leaves the machine. I did not want a badge that says "local" when the request is actually going somewhere else.&lt;/p&gt;

&lt;p&gt;The laboratory has five pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playground&lt;/strong&gt; is the conversation. I choose a model name, write a prompt, and the reply streams in as tokens. Enter sends. Shift+Enter inserts a line break. &lt;strong&gt;Stop&lt;/strong&gt; aborts the browser read. A stopped partial reply stays on screen so I can see where it cut off, but it is not saved. &lt;strong&gt;New conversation&lt;/strong&gt; clears the screen and does not delete anything already stored. &lt;strong&gt;Copy&lt;/strong&gt; copies a finished reply. A follow-up prompt includes the earlier user and assistant text from that screen. Messages that failed are left out. This release does not add a hidden system prompt. What I see is what is sent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compare&lt;/strong&gt; sends one prompt to two model names. Each side succeeds or fails on its own. If one tag is missing, that column shows the error and the other column can still show a reply. The page does not score them and does not say which one is better. A difference is not a ranking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt; lists the tags Ollama returns from &lt;code&gt;GET /api/tags&lt;/code&gt;. When Ollama includes family, parameter size, quantization, byte size, or a modified time, those fields are shown and labeled as reported by Ollama. I do not add benchmark scores, and I do not invent a download status. If the host cannot be reached, the model count stays unknown. The page does not pretend that zero models are installed when the check itself failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;History&lt;/strong&gt; is optional. Saving is off until I turn on "Save experiments on this computer." A finished playground or compare turn is then written to a SQLite file on this computer. Each row is the prompt, the reply, the model, the provider, the kind of turn, and the time. I can delete one row or all of them. Nothing in that file is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Settings&lt;/strong&gt; holds the Ollama host, an optional bearer token, the default model, the comparison model, and the maximum number of output tokens. &lt;strong&gt;Test connection&lt;/strong&gt; checks the form without saving it. A blank token field keeps a token that is already saved. "Clear the saved token" removes it. After a successful save the token is not shown again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ghost mode&lt;/strong&gt; sits at the bottom of the sidebar. It is a darker laboratory theme: obsidian background, emerald accents, gold lines. The choice stays in this browser only. It does not change the model, the host, or the settings file.&lt;/p&gt;

&lt;p&gt;What the program refuses to do is part of the design. It does not download model weights. It does not run &lt;code&gt;ollama pull&lt;/code&gt;. It does not execute a prompt as a shell command. It does not give the model a tool channel. It does not follow an HTTP redirect from the Ollama host. It does not replace a connection error, a missing model, or an empty body with a paragraph I wrote in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
ollama serve
ollama pull qwen2.5-coder:1.5b
ai-alchemist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="nx"&gt;\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serve&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qwen2.5-coder:1.5b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ai-alchemist&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ollama pull&lt;/code&gt; is something I run myself, after I have read the license on that tag. AI Alchemist will not run it. &lt;code&gt;qwen2.5-coder:1.5b&lt;/code&gt; is only an example of a small tag. Any tag Ollama can serve is a valid model name, as long as it is a single token.&lt;/p&gt;

&lt;p&gt;Then I open &lt;a href="http://127.0.0.1:8000" rel="noopener noreferrer"&gt;http://127.0.0.1:8000&lt;/a&gt;. The server binds to loopback. &lt;code&gt;python -m ai_alchemist&lt;/code&gt; starts the same process. &lt;code&gt;ai-alchemist --version&lt;/code&gt; prints &lt;code&gt;0.2.0&lt;/code&gt; and does not start the server.&lt;/p&gt;

&lt;p&gt;In Settings I leave the host at &lt;code&gt;http://127.0.0.1:11434&lt;/code&gt; unless Ollama is somewhere else. I put the tag I pulled in &lt;strong&gt;Default model&lt;/strong&gt;, choose &lt;strong&gt;Test connection&lt;/strong&gt;, then &lt;strong&gt;Save&lt;/strong&gt;. The sidebar chip should read that Ollama is local and should show how many models the host reported. If Ollama is not running, the chip reads &lt;code&gt;Ollama · Unavailable&lt;/code&gt; and a prompt returns that error. The page still opens.&lt;/p&gt;

&lt;p&gt;These photographs were taken in the browser while the laboratory was running at &lt;code&gt;127.0.0.1:8000&lt;/code&gt; and connected to an Ollama-compatible host.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fazq76wkjk6bl9bw0bt7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fazq76wkjk6bl9bw0bt7i.png" alt="AI Alchemist playground while it is running" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first photograph is the playground after a reply. The sidebar shows the name, the tagline "Turn Open Models Into Intelligent Creations," and the pages. The header shows the active model and that inference is local. The user prompt is on the right. The model reply is on the left, with &lt;strong&gt;Copy&lt;/strong&gt;. The prompt box, &lt;strong&gt;Send&lt;/strong&gt;, &lt;strong&gt;Stop&lt;/strong&gt;, &lt;strong&gt;New conversation&lt;/strong&gt;, and the save checkbox are at the bottom. Ghost mode is the green control in the sidebar. The chip says Ollama is local and that two models were reported.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa5f47yuwo89pzdkonykt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa5f47yuwo89pzdkonykt.png" alt="AI Alchemist model list while it is running" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second photograph is the model page. Both cards came from the host's tag list. Each card says "Local host" and "Listed by Ollama," and the description line starts with "Reported by Ollama," including family, parameter size, and quantization. One card is marked Default and the other Comparison. There is no score and no rank.&lt;/p&gt;

&lt;p&gt;A second model, if I want Compare, is another tag I have already pulled. I type both names and send one prompt. The two columns fill independently. I do not get a winner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The source is &lt;a href="https://github.com/gabutersproject/ai-alchemist" rel="noopener noreferrer"&gt;github.com/gabutersproject/ai-alchemist&lt;/a&gt;. The application is &lt;a href="https://github.com/gabutersproject/ai-alchemist/blob/master/LICENSE" rel="noopener noreferrer"&gt;MIT&lt;/a&gt;. Ollama, the model weights, and the Python dependencies keep their own licenses.&lt;/p&gt;

&lt;p&gt;The layout is one process, not a set of services.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/web/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Playground, Compare, Models, History, Settings, and Ghost mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/routes.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The JSON API and the token stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/providers/ollama.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /api/tags&lt;/code&gt; and &lt;code&gt;POST /api/chat&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/providers/registry.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The single place a provider is constructed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/config.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host, token, model names, and the history switch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/history.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQLite rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/ai_alchemist/cli.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;ai-alchemist&lt;/code&gt; process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The routes the page uses:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process health only. It does not claim a model is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/provider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether the saved host answered, and whether that host is local or remote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/models&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tags from that host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;PUT /api/settings&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Public settings. The token value is never included.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /api/settings/test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Probe the form without saving it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /api/chat/stream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Playground tokens as server-sent events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /api/compare&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One prompt, two model names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stored rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DELETE /api/history/{id}&lt;/code&gt; and &lt;code&gt;DELETE /api/history&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Delete one row, or every row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Settings live in &lt;code&gt;lab-config.json&lt;/code&gt; under the platform data directory: &lt;code&gt;$XDG_DATA_HOME/ai-alchemist/&lt;/code&gt; or &lt;code&gt;~/.local/share/ai-alchemist/&lt;/code&gt; on Linux, and &lt;code&gt;%LOCALAPPDATA%\ai-alchemist\&lt;/code&gt; on Windows. &lt;code&gt;AI_ALCHEMIST_DATA_DIR&lt;/code&gt; and &lt;code&gt;AI_ALCHEMIST_CONFIG&lt;/code&gt; override those paths. The file mode is owner read/write when the operating system allows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I split the work so the page does not know the HTTP shape of Ollama. The browser is static HTML, CSS, and JavaScript. FastAPI serves that page and the API. The Ollama client lives in one adapter. Routes ask that adapter to list models or stream a chat. Adding another provider later means a new class behind the same three operations: probe, list, and stream. This release only registers Ollama.&lt;/p&gt;

&lt;p&gt;A playground send is &lt;code&gt;POST /api/chat&lt;/code&gt; with &lt;code&gt;"stream": true&lt;/code&gt;. The body is the model name, the messages on screen, and Ollama's &lt;code&gt;num_predict&lt;/code&gt;, taken from &lt;code&gt;max_output_tokens&lt;/code&gt; (default 512, allowed range 16 to 4096). There is no &lt;code&gt;tools&lt;/code&gt; field. The server reads the newline-delimited JSON from Ollama and forwards each text fragment to the browser as a server-sent event. &lt;strong&gt;Stop&lt;/strong&gt; aborts that read. Closing the stream does not invent the rest of the sentence.&lt;/p&gt;

&lt;p&gt;Compare does not stream. It runs both generations with &lt;code&gt;asyncio.gather&lt;/code&gt; and returns both results, including a failure on only one side. I did that so a missing tag on the right does not hide a real reply on the left, and so the response has no winner field and no score.&lt;/p&gt;

&lt;p&gt;The model list is a parse of &lt;code&gt;/api/tags&lt;/code&gt;, not a catalog I maintain. A tag is "available" only because its name was in that JSON. The description string is built from &lt;code&gt;details.family&lt;/code&gt;, &lt;code&gt;details.parameter_size&lt;/code&gt;, and &lt;code&gt;details.quantization_level&lt;/code&gt; when those keys exist. If they do not, the card says Ollama listed the model and did not include a description. The size line is the &lt;code&gt;size&lt;/code&gt; integer converted to megabytes or gigabytes and labeled "reported by Ollama."&lt;/p&gt;

&lt;p&gt;Failure is explicit. A connection error, a timeout, a redirect, an HTTP error, a tool-call payload, or an empty answer becomes an error message that ends with "AI Alchemist did not invent a replacement answer." A 404 names the missing tag and prints the &lt;code&gt;ollama pull&lt;/code&gt; line for me to run myself. A redirect is not followed, so a configured host cannot bounce the prompt to another URL without the request failing. If a provider error echoes the saved bearer token, that string is replaced with &lt;code&gt;[redacted]&lt;/code&gt; before it reaches the page.&lt;/p&gt;

&lt;p&gt;The token itself never goes back to the browser. &lt;code&gt;GET /api/settings&lt;/code&gt; returns &lt;code&gt;token_configured: true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;. The value stays in &lt;code&gt;lab-config.json&lt;/code&gt; and is sent only as &lt;code&gt;Authorization: Bearer&lt;/code&gt; to the configured host. A host URL that embeds a username or password is rejected. The token belongs in the token field. A turn whose prompt or reply contains that token is not written to SQLite.&lt;/p&gt;

&lt;p&gt;History is two gates, not one. The setting &lt;code&gt;persist_experiments&lt;/code&gt; must be on, and the request must ask to save. The checkbox in the playground and the checkbox in Settings write the same flag. New conversation and Stop do not delete rows. Delete does.&lt;/p&gt;

&lt;p&gt;Ghost mode is a class on the document element, remembered in &lt;code&gt;localStorage&lt;/code&gt; under &lt;code&gt;ai-alchemist-ghost&lt;/code&gt;. The palette is obsidian &lt;code&gt;#0B0D10&lt;/code&gt;, emerald &lt;code&gt;#10B981&lt;/code&gt;, gold &lt;code&gt;#D4AF37&lt;/code&gt;, and soft white &lt;code&gt;#F5F7FA&lt;/code&gt;. Motion is a slow drift on the mark, and it is disabled when the browser asks for reduced motion. The text stays light on the dark background.&lt;/p&gt;

&lt;p&gt;Tests do not start Ollama. They use a fake provider for the API and &lt;code&gt;httpx.MockTransport&lt;/code&gt; for the adapter: a successful stream, a dead host, a missing model, a tool call, a redirect that must be requested only once, a token that must not reappear in an error, and history that must not be written until saving is enabled. &lt;code&gt;python -m pytest&lt;/code&gt; and &lt;code&gt;python -m ruff check src tests&lt;/code&gt; are the checks. A green test run is not a live generation. The live check is still Ollama, a tag I pulled, and a prompt in the playground.&lt;/p&gt;

&lt;p&gt;An earlier revision of this repository was a read-only terminal inspector for Git trees. That program is not what this post describes. It is not in the current tree. Git history still has it. The laboratory does not read that old &lt;code&gt;config.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The laboratory is only useful if I can change the model. Ollama is how that change stays practical. I pull a tag whose license I have actually read, I type that name, and the same page talks to it. I am not required to send the prompt to one hosted API, and I am not required to rewrite the interface when I switch from a 1.5B tag to another tag on the same server.&lt;/p&gt;

&lt;p&gt;Local inference is a real boundary only when the host address is loopback. That is why the chip and the model cards say "Local" or "Remote" from the host, not from a marketing string. A remote Ollama-compatible server is allowed. It is labeled remote, and the prompt is sent there. Open infrastructure includes that choice. It does not include hiding it.&lt;/p&gt;

&lt;p&gt;Open weights are not one license. Apache-2.0 and a research license are different deals, and an Ollama package can add its own terms. Downloadable weights are not automatically open source. The MIT license on this repository does not relicense Ollama or the weights. I read the model card for the tag I pull. The software can stay replaceable even when the weight license is narrow. Saying that out loud is part of using open models honestly.&lt;/p&gt;

&lt;p&gt;A local process also does not make a hostile model safe. It removes one mandatory data path. The prompt still goes to whatever host I configured, and the reply is still the model's reply. The page keeps those two facts separate: the chip tells me where the request went, and the answer is labeled as that model's text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;Week 4 asked for a ghost in the machine. I did not want a jump scare, and I did not want a horror skin on a chat box that still calls a vendor when the local service is gone.&lt;/p&gt;

&lt;p&gt;The ghost is the model that is actually resident on the computer. Ghost mode is the room it answers from: a dark laboratory, a gold mark, a little motion, and text I can still read. The silence matters as much as the theme. If Ollama is unreachable, the chip says unavailable and the prompt returns the connection error. There is no second voice that fills the pause and pretends the machine spoke.&lt;/p&gt;

&lt;p&gt;That is the whole of the spirit I was willing to build. A model can be present. When it is not present, the page should admit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize: Best Use of Ollama
&lt;/h2&gt;

&lt;p&gt;I am submitting AI Alchemist for the best use of Ollama. This section is the category I am writing for. It is not a claim that the prize has been awarded.&lt;/p&gt;

&lt;p&gt;Ollama is the inference engine, not a sentence in the introduction. Every reply the playground shows comes from &lt;code&gt;POST /api/chat&lt;/code&gt; on the configured host. Every card on the model page comes from &lt;code&gt;GET /api/tags&lt;/code&gt;. Compare is two of those chat calls, so two tags I have already installed can answer one prompt. The default host is Ollama's own loopback port. A missing tag produces the &lt;code&gt;ollama pull&lt;/code&gt; command and does not download the weights. A host that is not loopback is called remote. A host that does not answer is called unavailable, and the page does not invent the rest.&lt;/p&gt;

&lt;p&gt;That is the use I wanted from Ollama: run an open-weight model on the machine, see the tag I actually selected, and keep a second tag beside it when I want to look at both answers. The laboratory is the room around that use. Ollama is the machine inside the room.&lt;/p&gt;

&lt;p&gt;Happy Hacking!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Slow and Steady: local, open-source AI that helps you work without urgency or cloud lock-in</title>
      <dc:creator>amier_san09</dc:creator>
      <pubDate>Tue, 20 Oct 2026 21:04:22 +0000</pubDate>
      <link>https://dev.to/mhmmdamier/slow-and-steady-local-open-source-ai-that-helps-you-work-without-urgency-or-cloud-lock-in-51g</link>
      <guid>https://dev.to/mhmmdamier/slow-and-steady-local-open-source-ai-that-helps-you-work-without-urgency-or-cloud-lock-in-51g</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Slow and Steady is a personal AI workspace that runs on the machine in front of you.&lt;/p&gt;

&lt;p&gt;The idea is sloth-shaped on purpose. Most “AI assistants” ask you to open an account, paste private text into someone else’s API, and keep paying per token. That is a lot of fuss for a tool that is supposed to take work off your plate. This app does the opposite: install it once, talk to an open-source model on localhost, and keep the conversation in a SQLite file on disk.&lt;/p&gt;

&lt;p&gt;It helps with the work people put off: explain a hard topic, tidy a paragraph, sketch a project, walk through a code snippet, or just sit with a calm chat window. The computer does the drafting. You stay in the chair.&lt;/p&gt;

&lt;p&gt;There is a Demo Mode so you can use the whole interface before you download a model. Demo replies are scripted and labelled as scripted. They never pretend to be a language model, and they never silently replace a failed local request.&lt;/p&gt;

&lt;p&gt;When you are ready, connect Ollama, pick an installed model such as qwen2.5:1.5b, and chat for real. Streaming, stop, regenerate, Markdown, code highlighting, and conversation history all work the same way in both modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Chat with streaming, stop, regenerate, copy, and Markdown&lt;/li&gt;
&lt;li&gt;Keep history locally: create, rename, delete, continue, clear all&lt;/li&gt;
&lt;li&gt;Choose Demo Mode or any model already installed in Ollama&lt;/li&gt;
&lt;li&gt;Tune temperature, max tokens, system prompt, and streaming&lt;/li&gt;
&lt;li&gt;Show an honest status: Demo Mode, Local AI Ready, Model Loading, Generating, Model Not Found, Ollama Offline, Backend Unavailable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Local AI Ready&lt;/strong&gt; is only shown after the backend has checked that Ollama answers &lt;em&gt;and&lt;/em&gt; the selected model is installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqk6lus9q70pfr6zcv69e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqk6lus9q70pfr6zcv69e.jpg" alt="image" width="800" height="1422"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8zv0pqvid22pextbf74.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8zv0pqvid22pextbf74.jpg" alt="Image2" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/gabutersproject/slow-and-steady" rel="noopener noreferrer"&gt;https://github.com/gabutersproject/slow-and-steady&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run locally:&lt;/strong&gt; see the &lt;a href="//./README.md"&gt;README&lt;/a&gt; — open &lt;a href="http://127.0.0.1:4747" rel="noopener noreferrer"&gt;http://127.0.0.1:4747&lt;/a&gt; after starting both processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quick path for judges who do not want to pull a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./scripts/start-backend.sh    &lt;span class="c"&gt;# terminal 1&lt;/span&gt;
./scripts/start-frontend.sh   &lt;span class="c"&gt;# terminal 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Start Demo&lt;/strong&gt;. Suggested prompts are on the empty chat. Replies are labelled &lt;em&gt;scripted, not an AI model&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To use a real open-weight model (not automatic — you pull it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5:1.5b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then choose that model in the sidebar.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Two processes, both on loopback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser (Vite + React)  →  FastAPI on 127.0.0.1:8747  →  Ollama on 127.0.0.1:11434
                                ↓
                         SQLite (chats + settings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI never talks to a model. An abstract &lt;code&gt;AIProvider&lt;/code&gt; sits behind the API with two implementations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DemoProvider&lt;/strong&gt; — a small set of scripted replies, streamed in chunks so the UI path is the same as a real model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OllamaProvider&lt;/strong&gt; — local HTTP only (&lt;code&gt;/api/tags&lt;/code&gt;, &lt;code&gt;/api/chat&lt;/code&gt;). No cloud fallback. No auto-download.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stack:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React 19, TypeScript, Vite 8, Tailwind CSS 4, Lucide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python, FastAPI, Pydantic v2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference&lt;/td&gt;
&lt;td&gt;Ollama + open-weight models (example: Qwen2.5 1.5B)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;SQLite via aiosqlite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;Pytest (Ollama mocked) and Vitest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Security choices that match the theme of “do less, leak less”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bind the API to localhost; CORS only for the local Vite origin&lt;/li&gt;
&lt;li&gt;Validate temperature, token limits, message length, and model names&lt;/li&gt;
&lt;li&gt;One generation per conversation at a time (no interleaved history)&lt;/li&gt;
&lt;li&gt;Stop/cancel persists partial text as &lt;code&gt;cancelled&lt;/code&gt;, never as a finished answer&lt;/li&gt;
&lt;li&gt;No shell from model output, no secret leakage in error payloads, no arbitrary file tools in chat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests run without a downloaded model. Last run in the build environment: &lt;strong&gt;46 backend tests passed&lt;/strong&gt;, &lt;strong&gt;17 frontend tests passed&lt;/strong&gt;. Live generation against a real Ollama instance was not verified in that environment because Ollama was not installed there; offline detection and the mocked provider suite were.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The closed path for a chat app is easy: drop in a paid API key and ship. It is also the path that sends every draft, code snippet, and half-formed question to a server you do not run.&lt;/p&gt;

&lt;p&gt;Slow and Steady is built so the open pieces are the product, not a badge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your words stay here.&lt;/strong&gt; History is a local SQLite file. The model, when connected, is a process on &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No meter running.&lt;/strong&gt; After &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;pip install&lt;/code&gt;, and an optional &lt;code&gt;ollama pull&lt;/code&gt;, chat does not call a cloud LLM. You are not renting intelligence by the token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can swap the brain.&lt;/strong&gt; Any model Ollama lists is selectable. The UI does not care whether it is Qwen, Llama, or something else you pulled yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It still works when you are tired.&lt;/strong&gt; Demo Mode is the sloth door: explore the app at 11pm without waiting on a multi-gigabyte download. Connect a real model when you have the energy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed API would have been faster to stub and worse at the job. The job is a private, unhurried assistant that does the typing so you do not have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hacktoberfest Open-Source AI Challenge: Week 3&lt;/strong&gt; (theme: Slow and Steady) — overall&lt;/p&gt;

&lt;p&gt;No partner runtime is required for this project. It is meant to live on a laptop, not on a hosted GPU. I am not claiming Render, Gemma-as-a-service, DigitalOcean, or other partner categories unless those are added later with a real integration.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Sawhorse: a quoting desk that isn't allowed to invent a price</title>
      <dc:creator>amier_san09</dc:creator>
      <pubDate>Mon, 12 Oct 2026 23:22:29 +0000</pubDate>
      <link>https://dev.to/mhmmdamier/sawhorse-a-quoting-desk-that-isnt-allowed-to-invent-a-price-45ib</link>
      <guid>https://dev.to/mhmmdamier/sawhorse-a-quoting-desk-that-isnt-allowed-to-invent-a-price-45ib</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Sawhorse is a quoting desk for a one-person custom furniture shop.&lt;/p&gt;

&lt;p&gt;A maker gets a text like this: "We just bought a place in Catskill. Table for six, maybe eight with a leaf. Walnut if it's not crazy, otherwise white oak. My partner hates glossy finishes. Before Thanksgiving if that's even possible. Budget is around 4–5k. We're about 40 minutes from Hudson."&lt;/p&gt;

&lt;p&gt;The tedious part is not writing a polite reply. It is turning that note into hours, board feet, and an honest no. At this shop's rate card, black walnut is $5,592. White oak is $4,438. Thanksgiving falls inside a 10 to 14 week lead time, so the letter says that date is not a promise.&lt;/p&gt;

&lt;p&gt;The person who pays is an independent furniture maker, or the same kind of one-person shop in another trade: someone who already has a rate card, quotes from messy client texts, and does not want that text or those rates in a chatbot. A $40/month proposal tool still does not know a trestle base from a pedestal. Pasting the note into a general model produces a retail-shaped number the shop cannot build for.&lt;/p&gt;

&lt;p&gt;The sample shop is Mara Ellison Studio in Hudson, New York. Her card is $85 an hour, white oak at $14 a board foot, walnut at $32, and delivery zones for town, the mid-Hudson, and New York City. Two other notes are built in: Jonah's hard-maple desk with a cable hole, which lands at $1,913 against a $2,000 ceiling, and Andre's four-foot bookshelf to Brooklyn, which comes in at $1,837 against "under fifteen hundred."&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The sample desk is here: &lt;a href="https://gabuters-sawhorse.surge.sh" rel="noopener noreferrer"&gt;https://gabuters-sawhorse.surge.sh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open Priya's note. The weights download into your browser, about 1.1 GB, once, and then the model reads the note on that machine. White oak is the one to send, because walnut is over the range, and the letter says Thanksgiving is inside the lead time. You can change any field before a dollar is calculated. Nothing in the note is uploaded.&lt;/p&gt;

&lt;p&gt;A shop install is the same desk with the weights already on the laptop. &lt;code&gt;pnpm dev&lt;/code&gt; serves it at &lt;code&gt;http://127.0.0.1:43123&lt;/code&gt;. A short note usually reads in under a minute on a 4-core CPU with no GPU. The rate card and the glossary are editable. Saved tickets stay in a JSON file next to the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/gabutersproject" rel="noopener noreferrer"&gt;
        gabutersproject
      &lt;/a&gt; / &lt;a href="https://github.com/gabutersproject/hustle-ai-kit" rel="noopener noreferrer"&gt;
        hustle-ai-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Sawhorse&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Sawhorse is a quoting desk for a one-person furniture shop. Paste a client note. An open-weight model on this machine reads it into a job ticket. The shop's rate card, not the model, sets every dollar.&lt;/p&gt;
&lt;p&gt;The sample shop is Mara Ellison's, in Hudson, New York. Priya writes from Catskill about a dining table, walnut if it fits, otherwise white oak, no gloss, before Thanksgiving, four to five thousand dollars. Sawhorse prices both woods from Mara's hours and board feet, recommends the one that lands in the budget, and says plainly that Thanksgiving is inside a 10 to 14 week lead time.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why the open model is the product&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Qwen2.5-1.5B-Instruct (Q5_K_M, Apache-2.0) runs in &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; through &lt;code&gt;llama-cpp-python&lt;/code&gt;. No API key. The reply is constrained to a JSON schema that has no price field, and the prompt does not include the hourly rate or the lumber prices. A general…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/gabutersproject/hustle-ai-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The split that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;engine/reader.py&lt;/code&gt; asks the model for a job ticket. The JSON schema has a client, a piece, woods, a finish, a delivery zone, a budget, and the client's own words for the deadline. It has no price field.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;engine/pricer.py&lt;/code&gt; is ordinary arithmetic. Lumber is board feet times the species rate. Labor is hours times the shop rate. Delivery is a zone fee. A design drawing is added and, if the shop says so, credited back.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;engine/letter.py&lt;/code&gt; may print a dollar amount only after that math. A test fails if the letter contains any dollar figure that is not on the ticket.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data/pricebook.default.json&lt;/code&gt; is Mara's rate card. Edits and saved quotes stay in &lt;code&gt;data/&lt;/code&gt; on the machine and are not part of the repo.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# engine/reader.py — the grammar the model is held to
&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;piece&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pieces&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;species_primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;species&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unspecified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;species_alternate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;species&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finish&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;finishes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unspecified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delivery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;zones&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unspecified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;budget_low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;budget_high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deadline_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;# no price, no hours, no board feet
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The open-weight model is &lt;a href="https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF" rel="noopener noreferrer"&gt;Qwen2.5-1.5B-Instruct&lt;/a&gt;, quantized Q5_K_M, Apache-2.0, about 1.2 GB. Inference is &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; through &lt;code&gt;llama-cpp-python&lt;/code&gt;, on CPU. There is no API key.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create_chat_completion&lt;/code&gt; is called with &lt;code&gt;response_format&lt;/code&gt; set to a JSON schema. llama.cpp compiles that schema into a grammar, so the reply cannot leave the ticket shape. The prompt lists piece ids, wood ids, finish ids, delivery zones, and the shop glossary. It does not include the hourly rate or the lumber prices. "Catskill" means the mid-Hudson zone. "Hates glossy" means hardwax oil. "Hard maple" means maple. The model cannot anchor on a number it was never shown.&lt;/p&gt;

&lt;p&gt;A 1.5 billion parameter model still copies the shape of its examples. After the read, &lt;code&gt;apply_note_guards&lt;/code&gt; fixes two high-confidence misses: if the word "leaf" is in the note, the ticket includes a leaf; if the note says the room is "9 feet by 11" and the model copies 9 and 11 into the furniture dimensions, those values are dropped, because that was the room. The maker still sees every field and can change it before pricing.&lt;/p&gt;

&lt;p&gt;The desk is a Next.js app. A FastAPI process on localhost holds the weights. The browser talks only to Next, and Next proxies to that process, so a read never leaves the machine. The hosted sample keeps that split, only the process is the browser: llama.cpp runs there, and the rate card is the same Python. The glossary is the niche list a shop will actually edit: towns, finish phrases, species nicknames. The next note uses it. The same weights can take a LoRA later, trained on quotes the shop really sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;A frontier API would read one of these notes for less than a cent. That is not the bill that breaks a shop quoting fifteen small jobs in a month.&lt;/p&gt;

&lt;p&gt;What breaks it is a wrong number, or a leaked one. A general chat model asked to "quote this walnut table" reaches for retail: a round number and a confident email, at a price the shop cannot build for. Sending that loses the job or gives away the margin. The same model does not know that Catskill is about forty minutes from this shop. You can paste a glossary into a hosted prompt. You also paste the client's town, their budget, and sometimes why they need the table by a certain Thursday. In a town this size that note is not anonymous, and the rate card is the business.&lt;/p&gt;

&lt;p&gt;The open weight file is what makes the stricter design possible. llama.cpp will bind the reply to a grammar with no price field. The prompt is not given the rates, so there is nothing for the model to repeat. The weights are Apache-2.0, so the desk can be sold without renting the brain. They are small enough that a shop laptop with no GPU can run them. If the app disappeared, the rate card and the saved tickets would still be JSON files, and the model would still be a file beside them. Nobody gets to reprice the inference out from under a $4,000 job.&lt;/p&gt;

&lt;p&gt;The glossary is not a fine-tune. It is the adjustment a one-person shop will keep. A closed API does not hand you the weights to train later, and it does not hand you a grammar that forbids a price.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Crashtoberfest? Happy Hacking&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Touch Grass Sports AI – Offline AI That Gets You Outside</title>
      <dc:creator>amier_san09</dc:creator>
      <pubDate>Mon, 05 Oct 2026 21:59:06 +0000</pubDate>
      <link>https://dev.to/mhmmdamier/touch-grass-sports-ai-offline-ai-that-gets-you-outside-3f04</link>
      <guid>https://dev.to/mhmmdamier/touch-grass-sports-ai-offline-ai-that-gets-you-outside-3f04</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Touch Grass Sports AI&lt;/strong&gt; is a fully offline open-source AI that generates personalized outdoor sports challenges.  &lt;/p&gt;

&lt;p&gt;Its only mission: get you off the screen and into the real world.&lt;/p&gt;

&lt;p&gt;You pick your fitness level, sport, available time, and location. It replies with a concrete challenge that &lt;strong&gt;must&lt;/strong&gt; be done outside and always includes a real “touch grass” moment.&lt;/p&gt;

&lt;p&gt;Supports many sports: running, basketball, cycling, yoga, swimming, climbing, parkour, volleyball, tennis, skateboarding, martial arts, hiking, and more.&lt;/p&gt;

&lt;p&gt;Works in two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local LLM (Ollama) for creative challenges&lt;/li&gt;
&lt;li&gt;Pure rule-based mode (no model needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Web-UI&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sfd21ojdrxfoxg3rfho.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sfd21ojdrxfoxg3rfho.jpg" alt="Web UI screenshot" width="800" height="537"&gt;&lt;/a&gt;&lt;br&gt;
CLI&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6zws8p89boh47e8hoea.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6zws8p89boh47e8hoea.jpg" alt="CLI sceenshot" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/gabutersproject/touch-grass-sports-ai" rel="noopener noreferrer"&gt;https://github.com/gabutersproject/touch-grass-sports-ai&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Built entirely around open-source AI and local inference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ollama for running open-weight models locally (Llama 3.2, Phi-3, Gemma 2, etc.)&lt;/li&gt;
&lt;li&gt;Strict system prompts that force every output to be outdoor-only + include a touch-grass action&lt;/li&gt;
&lt;li&gt;Multi-language support (English + Indonesian)&lt;/li&gt;
&lt;li&gt;Gradio web UI + pure CLI&lt;/li&gt;
&lt;li&gt;Full rule-based fallback so it works even without any LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;A closed cloud API would have killed the whole idea.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs completely offline (useful on trails and parks with no signal)&lt;/li&gt;
&lt;li&gt;Zero data leaves the device&lt;/li&gt;
&lt;li&gt;Free forever, no API costs&lt;/li&gt;
&lt;li&gt;Fully customizable (swap models, edit prompts, expand challenges)&lt;/li&gt;
&lt;li&gt;Aligns with the “touch grass” philosophy — less dopamine, more real life
Open-weight models + local inference made this possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;Go outside. Touch grass. Move your body. 🏃‍♂️🌿&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>Hacktoberfest26: Beginner build for a Friend</title>
      <dc:creator>amier_san09</dc:creator>
      <pubDate>Fri, 02 Oct 2026 14:56:31 +0000</pubDate>
      <link>https://dev.to/mhmmdamier/hacktoberfest26-beginner-build-for-a-friend-5d41</link>
      <guid>https://dev.to/mhmmdamier/hacktoberfest26-beginner-build-for-a-friend-5d41</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Happy Hacktoberfest, everyone! 🎃&lt;/p&gt;

&lt;p&gt;I’m super excited to participate in the Hacktoberfest 2026 DEV Challenges, specifically the Weekend Challenge: Build for a Friend!. Open source is all about community, and what better way to kick things off than building something helpful for someone you know?&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
