<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ollama</title>
<link>https://coderlegion.com/tag/ollama</link>
<description>Powered by Question2Answer</description>
<item>
<title>What Is Context Size in Ollama? QWEN 3.8 Context Window Explained into Ollama</title>
<link>https://coderlegion.com/31590/what-is-context-size-in-ollama-qwen-3-8-context-window-explained-into-ollama</link>
<description>&lt;p&gt;Confused about context size in Ollama? Wondering what an AI context window is, why it matters, and how to configure it for Qwen 3.8 27B?&lt;/p&gt;
&lt;p&gt;In this beginner-friendly tutorial, you'll learn how context size works in local AI models, how to increase the context length in Ollama, and how to test different context sizes with real examples.&lt;/p&gt;
&lt;p&gt;I'll walk you through the basics of AI context windows, tokens, num_ctx, memory usage, and performance so you can run your local AI models more effectively.&lt;/p&gt;
&lt;p&gt;Whether you're just getting started with Ollama or experimenting with local LLMs, this video will help you understand context size and configure it with confidence.&lt;/p&gt;
&lt;p&gt;🚀 What You'll Learn&lt;/p&gt;
&lt;p&gt;✅ What is context size in Ollama?&lt;br&gt;
✅ AI context window explained in simple terms&lt;br&gt;
✅ Qwen 3.8 native context length vs. Ollama's configured context&lt;br&gt;
✅ How to increase context size using Ollama&lt;br&gt;
✅ How to test different context sizes with real examples&lt;br&gt;
✅ How context size affects RAM, VRAM, memory usage, and performance&lt;br&gt;
✅ How to check whether your configured context size is being used&lt;br&gt;
✅ Tips for choosing the right context size for local AI models&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/31590/what-is-context-size-in-ollama-qwen-3-8-context-window-explained-into-ollama</guid>
<pubDate>Sun, 18 Oct 2026 11:13:04 +0000</pubDate>
</item>
<item>
<title>What Is Context Size in Ollama? QWEN 3.8 Context Window Explained for Beginners</title>
<link>https://coderlegion.com/31589/what-is-context-size-in-ollama-qwen-3-8-context-window-explained-for-beginners</link>
<description>&lt;p&gt;Confused about context size in Ollama? Wondering what an AI context window is, why it matters, and how to configure it for Qwen 3.8 27B?&lt;/p&gt;
&lt;p&gt;In this beginner-friendly tutorial, you'll learn how context size works in local AI models, how to increase the context length in Ollama, and how to test different context sizes with real examples.&lt;/p&gt;
&lt;p&gt;I'll walk you through the basics of AI context windows, tokens, num_ctx, memory usage, and performance so you can run your local AI models more effectively.&lt;/p&gt;
&lt;p&gt;Whether you're just getting started with Ollama or experimenting with local LLMs, this video will help you understand context size and configure it with confidence.&lt;/p&gt;
&lt;p&gt;🚀 What You'll Learn&lt;/p&gt;
&lt;p&gt;✅ What is context size in Ollama?&lt;br&gt;
✅ AI context window explained in simple terms&lt;br&gt;
✅ Qwen 3.8 native context length vs. Ollama's configured context&lt;br&gt;
✅ How to increase context size using Ollama&lt;br&gt;
✅ How to test different context sizes with real examples&lt;br&gt;
✅ How context size affects RAM, VRAM, memory usage, and performance&lt;br&gt;
✅ How to check whether your configured context size is being used&lt;br&gt;
✅ Tips for choosing the right context size for local AI models&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/31589/what-is-context-size-in-ollama-qwen-3-8-context-window-explained-for-beginners</guid>
<pubDate>Sun, 18 Oct 2026 09:34:56 +0000</pubDate>
</item>
<item>
<title># Debugging ZORGAX: We Captured the Tool-Calling Loop Between Open WebUI and Ollama</title>
<link>https://coderlegion.com/30530/debugging-zorgax-we-captured-the-tool-calling-loop-between-open-webui-and-ollama</link>
<description>&lt;h2 id=&quot;debugging-zorgax-we-captured-the-tool-calling-loop-between-open-webui-and-ollama&quot;&gt;Debugging ZORGAX: We Captured the Tool-Calling Loop Between Open WebUI and Ollama&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;From 28 repeated function calls to a successful end-to-end test — and a new hypothesis about context-window limits.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In my previous article, &lt;strong&gt;Building ZORGAX: Turning a Local LLM into a DevOps AI Agent&lt;/strong&gt;, I described our effort to transform a local language model into a controlled infrastructure assistant for the MyZubster ecosystem.&lt;/p&gt;
&lt;p&gt;ZORGAX runs on Ollama, uses a custom model based on Qwen 2.5 3B, and connects to Open WebUI inside a Docker-based environment.&lt;/p&gt;
&lt;p&gt;Our objective is not to give AI unrestricted control over a server. We want an assistant capable of observing infrastructure, explaining problems, and eventually performing carefully authorized operations.&lt;/p&gt;
&lt;p&gt;But we encountered a problem that made one thing clear:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An AI agent that knows how to call a tool must also know when to stop.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When ZORGAX called &lt;code&gt;list_automations&lt;/code&gt;, the tool correctly returned an empty list. Yet the agent repeatedly called the same function instead of giving a final answer.&lt;/p&gt;
&lt;p&gt;In earlier tests, we observed 28 repeated calls, followed by another run with 16 calls.&lt;/p&gt;
&lt;p&gt;This article documents how we investigated that behavior, what we verified, and why the root cause is still an open research question.&lt;/p&gt;
&lt;h3 id=&quot;1-the-original-problem&quot;&gt;1. The original problem&lt;/h3&gt;
&lt;p&gt;We asked ZORGAX to check active automations.&lt;/p&gt;
&lt;p&gt;The tool returned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;automations&quot;: [],
  &quot;total&quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The expected answer was straightforward:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;There are currently no active automations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead, ZORGAX kept invoking &lt;code&gt;list_automations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For a read-only function, unnecessary repetition wastes time and computing resources.&lt;/p&gt;
&lt;p&gt;For a future DevOps agent with permission to restart services, deploy software, or modify infrastructure, uncontrolled repetition could be much more serious.&lt;/p&gt;
&lt;p&gt;We therefore decided to investigate the entire tool-calling workflow before enabling additional operational capabilities.&lt;/p&gt;
&lt;h3 id=&quot;2-testing-zorgax-directly-through-ollama&quot;&gt;2. Testing ZORGAX directly through Ollama&lt;/h3&gt;
&lt;p&gt;Our first experiment bypassed Open WebUI.&lt;/p&gt;
&lt;p&gt;We constructed a synthetic tool-calling conversation using Ollama's &lt;code&gt;/api/chat&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;p&gt;The sequence contained a user request, an assistant function call, and a matching tool response containing the empty automation list.&lt;/p&gt;
&lt;p&gt;ZORGAX completed the interaction successfully.&lt;/p&gt;
&lt;p&gt;It generated a final answer and did not request another tool call.&lt;/p&gt;
&lt;p&gt;This showed that the model &lt;strong&gt;could&lt;/strong&gt; handle an empty tool response correctly.&lt;/p&gt;
&lt;p&gt;However, the direct experiment used a simplified context. It was not identical to the original Open WebUI interaction, so it could not establish which component was responsible for the loop.&lt;/p&gt;
&lt;h3 id=&quot;3-a-suggestion-from-the-developer-community&quot;&gt;3. A suggestion from the developer community&lt;/h3&gt;
&lt;p&gt;After publishing the first article on Coder Legion, a commenter, &lt;strong&gt;lisarconnect&lt;/strong&gt;, suggested capturing the actual request sent to Ollama immediately after the first tool response.&lt;/p&gt;
&lt;p&gt;The key question was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does the next request preserve the assistant's tool call and the corresponding tool result?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This gave us a concrete investigation strategy.&lt;/p&gt;
&lt;p&gt;Instead of guessing whether the problem came from Qwen, Ollama, or Open WebUI, we could inspect the integration boundary directly.&lt;/p&gt;
&lt;p&gt;We also wanted to distinguish a successful empty result from a tool execution error.&lt;/p&gt;
&lt;p&gt;Those two situations should not trigger the same behavior.&lt;/p&gt;
&lt;h3 id=&quot;4-inspecting-open-webui-s-tool-call-handling&quot;&gt;4. Inspecting Open WebUI's tool-call handling&lt;/h3&gt;
&lt;p&gt;We examined the installed Open WebUI source and identified the code responsible for continuing conversations after tool execution.&lt;/p&gt;
&lt;p&gt;We then tested its actual &lt;code&gt;convert_output_to_messages()&lt;/code&gt; function using a synthetic fixture.&lt;/p&gt;
&lt;p&gt;The conversion preserved the expected structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &quot;role&quot;: &quot;assistant&quot;,
    &quot;content&quot;: &quot;&quot;,
    &quot;tool_calls&quot;: [
      {
        &quot;id&quot;: &quot;zorgax-test-001&quot;,
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
          &quot;name&quot;: &quot;list_automations&quot;,
          &quot;arguments&quot;: &quot;{}&quot;
        }
      }
    ]
  },
  {
    &quot;role&quot;: &quot;tool&quot;,
    &quot;tool_call_id&quot;: &quot;zorgax-test-001&quot;,
    &quot;content&quot;: &quot;{\&quot;automations\&quot;:[],\&quot;total\&quot;:0}&quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The assistant tool-call identifier matched the tool-result identifier.&lt;/p&gt;
&lt;p&gt;The empty result was preserved.&lt;/p&gt;
&lt;p&gt;This was an important finding, but it covered only the isolated message-conversion function.&lt;/p&gt;
&lt;p&gt;We still needed evidence from the actual HTTP requests reaching Ollama.&lt;/p&gt;
&lt;h3 id=&quot;5-building-a-separate-diagnostic-environment&quot;&gt;5. Building a separate diagnostic environment&lt;/h3&gt;
&lt;p&gt;To avoid affecting the main MyZubster installation, we deployed a separate Open WebUI instance.&lt;/p&gt;
&lt;p&gt;We also configured:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS: &quot;3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the Open WebUI build we inspected, the default tool-call iteration limit was 256.&lt;/p&gt;
&lt;p&gt;Reducing that limit helped prevent another long-running diagnostic loop.&lt;/p&gt;
&lt;p&gt;It was a safety measure, not a fix for the underlying behavior.&lt;/p&gt;
&lt;p&gt;We kept operational tools disabled and used a harmless simulated automation function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Tools:
    def list_automations(self) -&amp;gt; dict:
        &quot;&quot;&quot;Return a synthetic empty automation list.&quot;&quot;&quot;
        return {
            &quot;automations&quot;: [],
            &quot;total&quot;: 0
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function performs no infrastructure operation. It always returns the same predictable result.&lt;/p&gt;
&lt;h3 id=&quot;6-capturing-the-real-open-webui-ollama-requests&quot;&gt;6. Capturing the real Open WebUI → Ollama requests&lt;/h3&gt;
&lt;p&gt;We created a temporary HTTP proxy between the diagnostic Open WebUI instance and Ollama.&lt;/p&gt;
&lt;p&gt;The proxy forwarded requests while recording structural information such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model name&lt;/li&gt;
&lt;li&gt;Available tool names&lt;/li&gt;
&lt;li&gt;Message roles&lt;/li&gt;
&lt;li&gt;Tool-call identifiers&lt;/li&gt;
&lt;li&gt;Presence of the simulated empty result&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We intentionally avoided logging complete private prompts and credentials.&lt;/p&gt;
&lt;p&gt;One earlier captured request advertised approximately &lt;strong&gt;35 built-in tools&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That was very different from our successful direct Ollama test, which exposed only &lt;code&gt;list_automations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We also reproduced the repeated-call behavior in the isolated Open WebUI environment, where it stopped after reaching the configured three-iteration limit.&lt;/p&gt;
&lt;p&gt;At this stage, the number of tools became an interesting variable to investigate.&lt;/p&gt;
&lt;p&gt;But correlation is not causation.&lt;/p&gt;
&lt;p&gt;We had not yet demonstrated that having many available tools caused the repeated calls.&lt;/p&gt;
&lt;h3 id=&quot;7-the-successful-end-to-end-experiment&quot;&gt;7. The successful end-to-end experiment&lt;/h3&gt;
&lt;p&gt;We created a diagnostic model configuration called &lt;strong&gt;ZORGAX Diagnostic&lt;/strong&gt; and enabled only one harmless tool.&lt;/p&gt;
&lt;p&gt;Then we asked:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Controlla quali automazioni sono attive usando &lt;code&gt;list_automations&lt;/code&gt;. Se non ce ne sono, dimmelo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This time, ZORGAX called the tool once and responded correctly:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Non ci sono automazioni attive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The proxy captured the complete follow-up structure.&lt;/p&gt;
&lt;p&gt;The first request exposed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tools: ['list_automations']
Messages: ['user']
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next request contained:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tools: ['list_automations']
Messages: ['user', 'assistant', 'tool']
Empty automation result: True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was the evidence we had been looking for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open WebUI successfully transmitted the assistant tool call and the empty tool result to Ollama, and ZORGAX produced a final answer.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We later repeated the successful single-tool experiment.&lt;/p&gt;
&lt;p&gt;The proxy again confirmed that the tool result reached Ollama and that ZORGAX completed the task.&lt;/p&gt;
&lt;p&gt;This does not solve the original loop, but it substantially narrows our investigation.&lt;/p&gt;
&lt;h3 id=&quot;8-a-second-hypothesis-context-window-truncation&quot;&gt;8. A second hypothesis: context-window truncation&lt;/h3&gt;
&lt;p&gt;Another Coder Legion commenter, &lt;strong&gt;Mike Dabydeen&lt;/strong&gt;, suggested examining the model's available context window.&lt;/p&gt;
&lt;p&gt;His hypothesis was that a conversation containing a system prompt, extensive tool definitions, and chat history might exceed the available context.&lt;/p&gt;
&lt;p&gt;If older messages are truncated, the model might receive the latest tool result without sufficient context to understand the original request.&lt;/p&gt;
&lt;p&gt;That could potentially explain repeated tool calls.&lt;/p&gt;
&lt;p&gt;We investigated the Ollama configuration.&lt;/p&gt;
&lt;p&gt;Our environment uses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Ollama version: 0.32.14
Model: zorgax:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ZORGAX Modelfile does not explicitly specify &lt;code&gt;num_ctx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Ollama service also does not explicitly configure &lt;code&gt;OLLAMA_CONTEXT_LENGTH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After running a diagnostic inference, we queried the loaded model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;NAME             PROCESSOR    CONTEXT
zorgax:latest    100% CPU     4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;We confirmed that the successful single-tool test operated with a 4096-token context window.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We also updated our diagnostic proxy to record explicitly supplied values of &lt;code&gt;num_ctx&lt;/code&gt; and &lt;code&gt;num_predict&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The logging worked correctly.&lt;/p&gt;
&lt;p&gt;For real requests generated by our diagnostic Open WebUI configuration, &lt;code&gt;num_ctx&lt;/code&gt; was not explicitly present in the captured request options.&lt;/p&gt;
&lt;p&gt;That is different from saying the model had no context limit. Ollama still allocated a context window, as confirmed by &lt;code&gt;ollama ps&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;what-remains-unverified&quot;&gt;What remains unverified&lt;/h4&gt;
&lt;p&gt;We have &lt;strong&gt;not yet captured evidence that Ollama actually truncated messages during the problematic 35-tool interaction&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We have also not completed a controlled comparison of the same tool workload at 4096 versus 8192 tokens.&lt;/p&gt;
&lt;p&gt;Context truncation is therefore a plausible hypothesis, not our final diagnosis.&lt;/p&gt;
&lt;p&gt;The next experiment must keep the model, system instructions, tool schemas, and user request as consistent as possible while changing one variable at a time.&lt;/p&gt;
&lt;h3 id=&quot;9-what-these-experiments-taught-us&quot;&gt;9. What these experiments taught us&lt;/h3&gt;
&lt;p&gt;The investigation revealed three separate questions that matter for reliable AI agents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First: Did the tool execute correctly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An empty list can be a successful result. It must not automatically be treated as an error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second: Did the result reach the language model?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The conversation should preserve the relationship between the assistant's tool call and the tool's response.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third: Did the model use the result correctly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even a correctly transmitted response does not guarantee that a model will stop calling tools.&lt;/p&gt;
&lt;p&gt;All three stages need independent testing.&lt;/p&gt;
&lt;p&gt;For infrastructure agents, safeguards must also exist outside the model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Restricted tool permissions&lt;/li&gt;
&lt;li&gt;Bounded execution and retries&lt;/li&gt;
&lt;li&gt;Duplicate-call detection&lt;/li&gt;
&lt;li&gt;Idempotent operations where applicable&lt;/li&gt;
&lt;li&gt;Human authorization for consequential actions&lt;/li&gt;
&lt;li&gt;Auditable execution results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A prompt instruction saying “do not repeat the tool call” is not a substitute for these controls.&lt;/p&gt;
&lt;h3 id=&quot;10-opening-the-investigation-to-contributors&quot;&gt;10. Opening the investigation to contributors&lt;/h3&gt;
&lt;p&gt;We have published our diagnostic findings in the public MyZubster repository.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub investigation issue:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1577&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/issues/1577&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical reproduction guide:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/blob/main/docs/zorgax/TOOL-CALLING-INVESTIGATION-2026-10-09.md&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/blob/main/docs/zorgax/TOOL-CALLING-INVESTIGATION-2026-10-09.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Developers working with Ollama, Open WebUI, Qwen, Python, or local AI agents are invited to reproduce the experiments.&lt;/p&gt;
&lt;p&gt;Useful contributions include controlled context-window comparisons, tool-schema experiments, sanitized HTTP request analysis, and regression tests for empty results and repeated calls.&lt;/p&gt;
&lt;p&gt;Contributors can use their own systems. No access to the MyZubster production VPS or private infrastructure is required.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We started with an AI agent repeatedly invoking a function that had already returned a valid result.&lt;/p&gt;
&lt;p&gt;We now have a working diagnostic environment, a trace of the actual tool-result continuation, and two successful end-to-end single-tool experiments.&lt;/p&gt;
&lt;p&gt;We have also confirmed a 4096-token context allocation and identified a new hypothesis worth testing.&lt;/p&gt;
&lt;p&gt;The root cause of the original loop remains open.&lt;/p&gt;
&lt;p&gt;That is an important distinction.&lt;/p&gt;
&lt;p&gt;We are not claiming that we have fixed a bug simply because one controlled configuration works.&lt;/p&gt;
&lt;p&gt;We are building an evidence-based process to understand why the integration behaves differently under different conditions.&lt;/p&gt;
&lt;p&gt;For ZORGAX, the objective remains unchanged: create a useful local DevOps assistant that operates within clear security boundaries and earns trust through reproducible tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next milestone is a controlled 4096-versus-8192 context experiment with safe tool fixtures.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thanks to &lt;strong&gt;lisarconnect&lt;/strong&gt; and &lt;strong&gt;Mike Dabydeen&lt;/strong&gt; for their technical suggestions and to everyone following the MyZubster development process.&lt;/p&gt;
&lt;p&gt;The investigation continues in public.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; MyZubster Ecosystem&lt;br&gt;
&lt;strong&gt;Agent:&lt;/strong&gt; ZORGAX&lt;br&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Ollama, Qwen 2.5 3B, Open WebUI, Docker, Python, Ubuntu&lt;br&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Experimental; root cause under investigation&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Build in public. Test with evidence. Keep humans in control.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30530/debugging-zorgax-we-captured-the-tool-calling-loop-between-open-webui-and-ollama</guid>
<pubDate>Sat, 10 Oct 2026 03:09:08 +0000</pubDate>
</item>
<item>
<title>AI Privacy Is a Data-Flow Problem, Not a Model-Selection Problem</title>
<link>https://coderlegion.com/29198/ai-privacy-is-a-data-flow-problem-not-a-model-selection-problem</link>
<description>&lt;p&gt;AI Privacy Is a Data-Flow Problem, Not a Model-Selection Problem&lt;br&gt;
How a conversation with a skeptical programmer led us to redesign Zorgax around trust boundaries, fail-closed processing, local AI, controlled egress, and auditable decisions&lt;br&gt;
A conversation with another programmer recently forced me to reconsider what we mean when we call an AI system “private.”&lt;br&gt;
It wasn't a discussion about model benchmarks, context windows, or whether one LLM was better than another.&lt;br&gt;
I was speaking with Nicola's father, a programmer who works in a company where the use of AI has to be considered carefully. Lawyers are involved in deciding what can be authorized because employees may handle personal information, internal company information, or other data that shouldn't casually be sent to an external AI provider.&lt;br&gt;
His concern could be reduced to a very practical question:&lt;br&gt;
What does the AI actually take, and where does that information go?&lt;/p&gt;
&lt;p&gt;That question led us to inspect the AI architecture we're building around Zorgax in MyZubster.&lt;br&gt;
And we found something worth writing about.&lt;br&gt;
“We Use Ollama” Doesn't Prove Anything&lt;br&gt;
Inside our AI routing logic, we had a provider identified as ollama.&lt;br&gt;
That sounds straightforward:&lt;br&gt;
OpenAI → external&lt;br&gt;
Ollama → local&lt;/p&gt;
&lt;p&gt;Except that's not what the complete data flow guaranteed.&lt;br&gt;
When we traced the fallback path instead of trusting the provider label, we found that the non-OpenAI path could eventually reach a public AI gateway.&lt;br&gt;
The architecture effectively allowed this:&lt;br&gt;
User prompt&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; │
 ▼
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Web search&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; │
 ▼
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Model router&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; │
 ├──── OpenAI
 │
 └──── &quot;ollama&quot;
            │
            ▼
      Public AI gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also had a genuinely local Ollama integration elsewhere in the system.&lt;br&gt;
So the problem wasn't Ollama.&lt;br&gt;
The problem was architectural:&lt;br&gt;
We were using a provider name as evidence of a privacy property.&lt;/p&gt;
&lt;p&gt;But privacy doesn't care what a variable is called.&lt;br&gt;
It cares where the bytes go.&lt;br&gt;
Start With the Trust Boundary&lt;br&gt;
That changed the question we wanted Zorgax to answer.&lt;br&gt;
Instead of starting with:&lt;br&gt;
Which model should process this request?&lt;/p&gt;
&lt;p&gt;we now start with:&lt;br&gt;
Is this data permitted to cross an external trust boundary?&lt;/p&gt;
&lt;p&gt;Only after answering that should model selection happen.&lt;br&gt;
Consider a prompt containing:&lt;br&gt;
Customer: &lt;em&gt;Emails are not allowed&lt;/em&gt;&lt;br&gt;
Internal project: Aurora&lt;br&gt;
API token: ...&lt;/p&gt;
&lt;p&gt;The first decision shouldn't be whether GPT or another model would produce the best response.&lt;br&gt;
The first decision is whether that payload is allowed to leave the machine at all.&lt;br&gt;
That led us to introduce an explicit privacy policy layer.&lt;br&gt;
Four Data Classes&lt;br&gt;
We currently distinguish four classes:&lt;br&gt;
PUBLIC&lt;br&gt;
INTERNAL&lt;br&gt;
CONFIDENTIAL&lt;br&gt;
PII&lt;/p&gt;
&lt;p&gt;Those feed into processing modes:&lt;br&gt;
EXTERNAL_ALLOWED&lt;br&gt;
LOCAL_ONLY&lt;br&gt;
DENY&lt;/p&gt;
&lt;p&gt;One design choice here matters more than it initially appears:&lt;br&gt;
missing classification fails closed.&lt;br&gt;
If Zorgax doesn't know what the data is, it doesn't assume that it is public.&lt;br&gt;
It defaults toward:&lt;br&gt;
INTERNAL&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LOCAL_ONLY&lt;/p&gt;
&lt;p&gt;For an enterprise-oriented AI system, I think this is an important inversion.&lt;br&gt;
Many systems effectively behave like this:&lt;br&gt;
not known to be sensitive&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    =
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;probably safe to send&lt;/p&gt;
&lt;p&gt;We wanted:&lt;br&gt;
not known to be public&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    =
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;do not send externally&lt;/p&gt;
&lt;p&gt;PUBLIC Is Not Permission&lt;br&gt;
Classification and authorization are different concepts.&lt;br&gt;
This distinction became one of the most important parts of the implementation.&lt;br&gt;
Even if information is classified as:&lt;br&gt;
PUBLIC&lt;/p&gt;
&lt;p&gt;Zorgax still doesn't automatically get permission to send it to an external AI/search provider.&lt;br&gt;
External processing requires another explicit condition:&lt;br&gt;
externalProcessingAllowed === true&lt;/p&gt;
&lt;p&gt;So the effective decision looks more like this:&lt;br&gt;
PUBLIC&lt;br&gt;
  +&lt;br&gt;
trusted external authorization&lt;br&gt;
  =&lt;br&gt;
external processing may proceed&lt;/p&gt;
&lt;p&gt;while:&lt;br&gt;
PUBLIC&lt;br&gt;
  +&lt;br&gt;
no external authorization&lt;br&gt;
  =&lt;br&gt;
external processing denied&lt;/p&gt;
&lt;p&gt;And:&lt;br&gt;
INTERNAL / CONFIDENTIAL / PII&lt;br&gt;
  =&lt;br&gt;
LOCAL_ONLY&lt;/p&gt;
&lt;p&gt;Why separate the two?&lt;br&gt;
Because:&lt;br&gt;
“What kind of information is this?” and “Are we authorized to send it there?” are not the same question.&lt;/p&gt;
&lt;p&gt;This also means we don't want a browser to simply submit something equivalent to:&lt;br&gt;
{&lt;br&gt;
  &quot;classification&quot;: &quot;PUBLIC&quot;,&lt;br&gt;
  &quot;externalProcessingAllowed&quot;: true&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;and grant itself permission.&lt;br&gt;
We added tests around the public routes specifically to prevent client-controlled classification or authorization from silently becoming trusted server-side policy.&lt;br&gt;
Similarly, having access to a paid feature is not treated as privacy authorization.&lt;br&gt;
Entitlement and data governance are separate concerns.&lt;br&gt;
Sensitive-Data Detection Gets a Veto&lt;br&gt;
There is another problem with classifications: they can be wrong.&lt;br&gt;
Imagine receiving:&lt;br&gt;
classification = PUBLIC&lt;/p&gt;
&lt;p&gt;alongside:&lt;br&gt;
Please analyze &lt;em&gt;Emails are not allowed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Or an API token.&lt;br&gt;
Or private-key material.&lt;br&gt;
The system shouldn't say:&lt;br&gt;
“The caller said PUBLIC, so we're good.”&lt;/p&gt;
&lt;p&gt;The policy layer therefore performs sensitive-data detection independently.&lt;br&gt;
The current implementation recognizes patterns including:&lt;br&gt;
email addresses&lt;br&gt;
IPv4 addresses&lt;br&gt;
Italian fiscal codes&lt;br&gt;
IBANs&lt;br&gt;
payment-card candidates&lt;br&gt;
Bearer tokens&lt;br&gt;
API keys&lt;br&gt;
secret/password/token assignments&lt;br&gt;
private-key material&lt;/p&gt;
&lt;p&gt;If those detectors identify sensitive content, they can override a PUBLIC declaration.&lt;br&gt;
In other words:&lt;br&gt;
declared: PUBLIC&lt;br&gt;
detected: PII&lt;/p&gt;
&lt;p&gt;effective result: PII&lt;/p&gt;
&lt;p&gt;External authorization doesn't override that either.&lt;br&gt;
This creates a useful security invariant:&lt;br&gt;
A declaration cannot downgrade detected sensitive information into externally processable data.&lt;/p&gt;
&lt;p&gt;“Local” Now Has a Technical Definition&lt;br&gt;
We also implemented a dedicated local AI path.&lt;br&gt;
Zorgax can use a local Ollama endpoint, with the default targeting:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;http://127.0.0.1:11434&quot;&gt;http://127.0.0.1:11434&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But there's an important restriction.&lt;br&gt;
For LOCAL_ONLY, we don't accept an arbitrary Ollama URL and assume it is local.&lt;br&gt;
The current implementation restricts the endpoint to explicit loopback hosts:&lt;br&gt;
127.0.0.1&lt;br&gt;
localhost&lt;br&gt;
::1&lt;/p&gt;
&lt;p&gt;A server elsewhere on the private network may still be trustworthy in a particular deployment.&lt;br&gt;
But it isn't the same trust boundary.&lt;br&gt;
If we support that later, it should probably become an explicit policy concept rather than quietly redefining LOCAL_ONLY.&lt;br&gt;
This is one of the broader lessons from the work:&lt;br&gt;
Security properties become useful when their definitions are narrow enough to test.&lt;/p&gt;
&lt;p&gt;Search Is Egress Too&lt;br&gt;
AI privacy discussions often focus entirely on the LLM endpoint.&lt;br&gt;
But consider this pipeline:&lt;br&gt;
Sensitive prompt&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  │
  ▼
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Search provider&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  │
  ▼
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Local LLM&lt;/p&gt;
&lt;p&gt;The final inference may be completely local.&lt;br&gt;
The sensitive information may already have left the system.&lt;br&gt;
Zorgax can interact with external search providers, so web research has to pass through the same privacy thinking.&lt;br&gt;
We therefore treat external destinations such as:&lt;br&gt;
OpenAI&lt;br&gt;
public AI gateways&lt;br&gt;
web-search providers&lt;/p&gt;
&lt;p&gt;as egress boundaries.&lt;br&gt;
That required changing the ordering of operations.&lt;br&gt;
The dangerous mental model is:&lt;br&gt;
prompt&lt;br&gt;
  ↓&lt;br&gt;
search&lt;br&gt;
  ↓&lt;br&gt;
choose model&lt;br&gt;
  ↓&lt;br&gt;
privacy somehow happens here&lt;/p&gt;
&lt;p&gt;The architecture we want is:&lt;br&gt;
prompt&lt;br&gt;
  ↓&lt;br&gt;
classification&lt;br&gt;
  ↓&lt;br&gt;
policy&lt;br&gt;
  ↓&lt;br&gt;
allowed destinations&lt;br&gt;
  ↓&lt;br&gt;
minimization&lt;br&gt;
  ↓&lt;br&gt;
search/model execution&lt;/p&gt;
&lt;p&gt;Privacy has to happen before the first external request, not just before LLM inference.&lt;br&gt;
Minimize Even Authorized Data&lt;br&gt;
Once external processing is authorized, we still don't want to send more than necessary.&lt;br&gt;
So we introduced an external-processing minimization step.&lt;br&gt;
It can:&lt;br&gt;
normalize input&lt;br&gt;
redact recognized sensitive patterns&lt;br&gt;
limit payload size&lt;br&gt;
minimize history&lt;/p&gt;
&lt;p&gt;before a permitted external call.&lt;br&gt;
There is an important boundary here too.&lt;br&gt;
Minimization is defense in depth.&lt;br&gt;
It is not used to turn forbidden data into permitted data.&lt;br&gt;
We don't want:&lt;br&gt;
PII&lt;br&gt;
 ↓&lt;br&gt;
redact some pieces&lt;br&gt;
 ↓&lt;br&gt;
declare it safe&lt;br&gt;
 ↓&lt;br&gt;
external provider&lt;/p&gt;
&lt;p&gt;Instead, the privacy decision is made against the original data.&lt;br&gt;
Only an already-permitted external operation gets minimized before transmission.&lt;br&gt;
Don't Forget Conversation History&lt;br&gt;
An assistant request isn't just the latest message.&lt;br&gt;
Suppose the conversation is:&lt;br&gt;
User:&lt;br&gt;
My customer's email is &lt;em&gt;Emails are not allowed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;User:&lt;br&gt;
Can you summarize what I just told you?&lt;/p&gt;
&lt;p&gt;The second message contains no PII by itself.&lt;br&gt;
But sending the conversation history externally would.&lt;br&gt;
So Zorgax evaluates the relevant history when determining the privacy mode.&lt;br&gt;
Sensitive context in previous messages can therefore force the request into local processing.&lt;br&gt;
This seems obvious once you see it.&lt;br&gt;
It's also very easy to miss when privacy logic is implemented only around a message field.&lt;br&gt;
The boundary needs to inspect what will actually be transmitted.&lt;br&gt;
Audit the Decision, Not the Secret&lt;br&gt;
Enterprise systems also need observability.&lt;br&gt;
We introduced a privacy audit layer that can record properties such as:&lt;br&gt;
classification&lt;br&gt;
processing mode&lt;br&gt;
destination&lt;br&gt;
decision&lt;br&gt;
reason&lt;br&gt;
content digest&lt;br&gt;
timestamp&lt;/p&gt;
&lt;p&gt;But the audit record deliberately avoids storing the raw prompt or conversation history.&lt;br&gt;
Otherwise you risk building this:&lt;br&gt;
Privacy system:&lt;br&gt;
&quot;Don't send this sensitive prompt externally.&quot;&lt;/p&gt;
&lt;p&gt;Audit system:&lt;br&gt;
&quot;Great, I'll make another copy of the entire sensitive prompt.&quot;&lt;/p&gt;
&lt;p&gt;The local audit file is also created with restrictive filesystem permissions.&lt;br&gt;
This is not yet a complete enterprise audit platform.&lt;br&gt;
We still have work to do around centralized audit lifecycle, configurable retention, administrative access and provider allowlists.&lt;br&gt;
But the principle is already useful:&lt;br&gt;
Record enough to understand the privacy decision without unnecessarily reproducing the sensitive payload.&lt;/p&gt;
&lt;p&gt;The Resulting Architecture&lt;br&gt;
The new flow is conceptually closer to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                  USER
                    │
                    ▼
            ┌──────────────┐
            │ CLASSIFY DATA│
            └──────┬───────┘
                   │
                   ▼
            ┌──────────────┐
            │ POLICY ENGINE│
            └──────┬───────┘
                   │
      ┌────────────┼────────────┐
      │            │            │
      ▼            ▼            ▼
   PUBLIC       LOCAL_ONLY      DENY
      │            │             │
      ▼            │             X
Authorized?        │
  │      │          │
 NO     YES         │
  X      │          │
         ▼          ▼
    MINIMIZATION  LOOPBACK
         │         OLLAMA
         ▼
   EXTERNAL EGRESS
         │
  ┌──────┼────────┐
  ▼      ▼        ▼
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;   OpenAI  Search   Public&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                Gateway

          │
          ▼
    PRIVACY AUDIT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most important box isn't Ollama.&lt;br&gt;
It isn't OpenAI either.&lt;br&gt;
It's the policy decision before either one.&lt;br&gt;
Testing the Network Boundary&lt;br&gt;
We didn't want to stop at unit tests for classification.&lt;br&gt;
A test like this is useful:&lt;br&gt;
PII → LOCAL_ONLY&lt;/p&gt;
&lt;p&gt;But it doesn't demonstrate that another code path won't accidentally perform an external fetch.&lt;br&gt;
So some of our runtime tests work from the opposite direction.&lt;br&gt;
For a PII request, the mocked network layer rejects any request that isn't going to the expected loopback Ollama endpoint.&lt;br&gt;
If a future regression tries to send that data to an external AI or search destination, the test fails.&lt;br&gt;
We also test cases including:&lt;br&gt;
PII in conversation history → local processing&lt;/p&gt;
&lt;p&gt;remote OLLAMA_URL → rejected for LOCAL_ONLY&lt;/p&gt;
&lt;p&gt;PUBLIC without explicit external authorization → external denied&lt;/p&gt;
&lt;p&gt;PUBLIC containing detected secrets → local&lt;/p&gt;
&lt;p&gt;client-controlled privacy authorization → not trusted&lt;/p&gt;
&lt;p&gt;external payload → minimized&lt;/p&gt;
&lt;p&gt;privacy audit → no raw prompt/history&lt;/p&gt;
&lt;p&gt;Before integration, the combined Evidence Graph and privacy test gate completed:&lt;br&gt;
19 test suites passed&lt;br&gt;
97 tests passed&lt;br&gt;
0 failed&lt;/p&gt;
&lt;p&gt;Tests don't establish legal compliance.&lt;br&gt;
They do give us evidence about specific technical invariants.&lt;br&gt;
That's the level at which I think privacy engineering becomes much more concrete.&lt;br&gt;
Deployment Had One More Lesson for Us&lt;br&gt;
After testing and merging the architecture, we restarted the MyZubster backend.&lt;br&gt;
It didn't come back.&lt;br&gt;
The privacy code wasn't responsible.&lt;br&gt;
A systemd drop-in expected a startup wrapper that was missing from the working tree.&lt;br&gt;
We found the historical wrapper elsewhere on the server and compared it with the version originally committed to Git.&lt;br&gt;
Their SHA-256 hashes matched exactly.&lt;br&gt;
We restored it.&lt;br&gt;
The application then exposed another pre-existing problem: two Italian strings in an authentication route contained ASCII apostrophes inside single-quoted JavaScript strings.&lt;br&gt;
Something as innocent as:&lt;br&gt;
l'automazione&lt;/p&gt;
&lt;p&gt;was enough to stop Node from parsing the route.&lt;br&gt;
After correcting those strings and verifying the syntax, we started the service again.&lt;br&gt;
This time:&lt;br&gt;
active (running)&lt;/p&gt;
&lt;p&gt;Connected to MongoDB&lt;/p&gt;
&lt;p&gt;MyZubster Gateway listening on 127.0.0.1:5003&lt;/p&gt;
&lt;p&gt;Backend MongoDB initialized&lt;/p&gt;
&lt;p&gt;It was an unrelated deployment issue, but it reinforced the exact principle behind the privacy redesign.&lt;br&gt;
A repository saying something is correct isn't the final evidence.&lt;br&gt;
A configuration saying something is local isn't the final evidence.&lt;br&gt;
A provider being named ollama isn't the final evidence.&lt;br&gt;
The runtime matters.&lt;br&gt;
What This Does — and Doesn't — Mean&lt;br&gt;
I want to be careful about one thing.&lt;br&gt;
We're not calling this “GDPR compliant AI.”&lt;br&gt;
Technical privacy controls are only part of the picture.&lt;br&gt;
Real organizational compliance can involve legal basis, contracts, retention, governance, access control, processor relationships, organizational procedures and the details of the deployment itself.&lt;br&gt;
What we've implemented is narrower and easier to verify:&lt;br&gt;
Technical controls intended to prevent sensitive or unauthorized AI data from silently crossing an external trust boundary.&lt;/p&gt;
&lt;p&gt;There is more to build.&lt;br&gt;
But that's a property we can reason about.&lt;br&gt;
And, importantly, test.&lt;br&gt;
Back to the Conversation That Started It&lt;br&gt;
The interesting thing about my conversation with Nicola's father is that he wasn't really asking whether AI was intelligent enough.&lt;br&gt;
He was asking whether a company could understand and control what happened to its information.&lt;br&gt;
That leads to a different set of questions for AI developers:&lt;br&gt;
What information enters the AI system?&lt;/p&gt;
&lt;p&gt;How is it classified?&lt;/p&gt;
&lt;p&gt;Which information can leave the organization?&lt;/p&gt;
&lt;p&gt;Which providers can receive it?&lt;/p&gt;
&lt;p&gt;Who authorizes external processing?&lt;/p&gt;
&lt;p&gt;Can a client grant itself that authorization?&lt;/p&gt;
&lt;p&gt;What happens when sensitive data is detected?&lt;/p&gt;
&lt;p&gt;Does conversation history count?&lt;/p&gt;
&lt;p&gt;What exactly does &quot;local&quot; mean?&lt;/p&gt;
&lt;p&gt;What is recorded in the audit trail?&lt;/p&gt;
&lt;p&gt;Which test fails if the boundary is accidentally removed?&lt;/p&gt;
&lt;p&gt;And is the expected architecture actually running?&lt;/p&gt;
&lt;p&gt;Those questions aren't as exciting as announcing a new model integration.&lt;br&gt;
But if AI is going to move deeper into companies, I suspect they're going to become some of the most important questions we ask.&lt;br&gt;
What Changed for Us&lt;br&gt;
At the beginning, we thought about privacy partly in terms of providers:&lt;br&gt;
OpenAI = external&lt;br&gt;
Ollama = local&lt;/p&gt;
&lt;p&gt;Now we think about it in terms of data paths and trust boundaries.&lt;br&gt;
That's a much stronger model.&lt;br&gt;
Because the important question isn't:&lt;br&gt;
Which AI are you using?&lt;/p&gt;
&lt;p&gt;It's:&lt;br&gt;
What path is this particular piece of information allowed to travel?&lt;/p&gt;
&lt;p&gt;That's what we're now building into Zorgax.&lt;br&gt;
And the rule we're taking forward is simple:&lt;br&gt;
Don't trust the label.&lt;br&gt;
Trace the data flow.&lt;br&gt;
Enforce the boundary.&lt;br&gt;
Test the egress.&lt;br&gt;
Verify the runtime.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29198/ai-privacy-is-a-data-flow-problem-not-a-model-selection-problem</guid>
<pubDate>Wed, 30 Sep 2026 06:54:25 +0000</pubDate>
</item>
<item>
<title>Qwen2.5 7B vs Qwen3 4B &amp; 8B for Writing Correction: 60 Local Ollama Responses on Windows</title>
<link>https://coderlegion.com/28819/qwen2-5-7b-vs-qwen3-4b-8b-for-writing-correction-60-local-ollama-responses-on-windows</link>
<description>&lt;p&gt;I expected Qwen2.5 7B to retain a noticeable advantage over the smaller Qwen3 4B model for writing correction.&lt;/p&gt;
&lt;p&gt;In this experiment, it didn't.&lt;/p&gt;
&lt;p&gt;Across the same 20 paired writing cases, Qwen2.5 7B and Qwen3 4B produced exactly the same complete-case outcome: both succeeded on the same 18 cases and failed on the same 2.&lt;/p&gt;
&lt;p&gt;The difference became much more noticeable in local execution time: in the tested cold-start setup, Qwen3 4B averaged &lt;strong&gt;23.99 seconds&lt;/strong&gt;, compared with &lt;strong&gt;54.37 seconds&lt;/strong&gt; for Qwen2.5 7B.&lt;/p&gt;
&lt;p&gt;That raised another practical question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can a smaller model from a newer generation reproduce the practical writing-correction performance of a larger model from the previous generation?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This benchmark is a follow-up to my earlier &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/26477/qwen3-4b-vs-8b-vs-14b-for-writing-correction-60-local-ollama-responses-on-windows&quot;&gt;Qwen3 4B vs 8B vs 14B writing-correction experiment&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In that first study, Qwen3 8B and 14B both achieved &lt;strong&gt;19/20 complete cases&lt;/strong&gt;, while the mean cold-start time for Qwen3 8B was roughly half that of Qwen3 14B.&lt;/p&gt;
&lt;p&gt;This second experiment approaches the same model-selection question from another direction: instead of comparing sizes within Qwen3, it compares &lt;strong&gt;Qwen2.5 7B with Qwen3 4B and Qwen3 8B&lt;/strong&gt; under the same paired writing-coach evaluation framework.&lt;/p&gt;
&lt;h3 id=&quot;benchmark-setup&quot;&gt;Benchmark setup&lt;/h3&gt;
&lt;p&gt;The benchmark uses 20 paired writing cases, including 16 sentences with expected errors and 4 already-correct controls.&lt;/p&gt;
&lt;p&gt;Each case was evaluated with the same frozen reference and the same evaluation framework across three locally hosted Qwen models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qwen2.5:7b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qwen3:4b-q4_K_M&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qwen3:8b-q4_K_M&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This produced 60 local responses in total.&lt;/p&gt;
&lt;p&gt;The models were executed through Ollama on Windows under the same benchmark conditions. The objective was not to identify a universal “best model,” but to compare several dimensions separately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;complete correction of the expected errors;&lt;/li&gt;
&lt;li&gt;expected-correction coverage;&lt;/li&gt;
&lt;li&gt;localization of the error regions;&lt;/li&gt;
&lt;li&gt;preservation of the original facts;&lt;/li&gt;
&lt;li&gt;compliance with the requested output structure;&lt;/li&gt;
&lt;li&gt;explanation-language and consistency checks;&lt;/li&gt;
&lt;li&gt;local cold-start execution time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the primary metric discussed here, a case was considered complete only when all expected correction requirements for that case were satisfied without an incorrect replacement, false error, or configured fact loss.&lt;/p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results&lt;/h3&gt;
&lt;h4 id=&quot;complete-case-correction&quot;&gt;Complete-case correction&lt;/h4&gt;
&lt;p&gt;The primary correction results were close:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 18/20 complete cases — 90%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen2.5 7B:&lt;/strong&gt; 18/20 complete cases — 90%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 19/20 complete cases — 95%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the primary metric alone, Qwen3 8B completed one additional case in this run.&lt;/p&gt;
&lt;p&gt;The more distinctive result, however, was the paired comparison between Qwen2.5 7B and Qwen3 4B.&lt;/p&gt;
&lt;p&gt;They did not merely finish with the same 18/20 score. They succeeded on the same 18 cases and failed on the same 2 cases.&lt;/p&gt;
&lt;p&gt;Across all 20 paired cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;both succeeded: &lt;strong&gt;18/20&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;both failed: &lt;strong&gt;2/20&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;different complete-case outcomes: &lt;strong&gt;0/20&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, for this specific 20-case writing benchmark, moving from Qwen2.5 7B to Qwen3 4B preserved the same primary correction outcome case by case.&lt;/p&gt;
&lt;p&gt;This should not be interpreted as evidence that the two models are universally equivalent. It means that no complete-case difference was observed between them within this controlled test set.&lt;/p&gt;
&lt;h4 id=&quot;local-execution-time&quot;&gt;Local execution time&lt;/h4&gt;
&lt;p&gt;The difference became much larger when local cold-start execution time was considered.&lt;/p&gt;
&lt;p&gt;In the tested configuration, the mean total cold-start times were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 23.99 s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen2.5 7B:&lt;/strong&gt; 54.37 s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 60.24 s&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qwen3 4B therefore required less than half the mean cold-start time of Qwen2.5 7B in this run.&lt;/p&gt;
&lt;p&gt;Expressed as a ratio, Qwen3 4B was approximately &lt;strong&gt;2.27× faster&lt;/strong&gt; than Qwen2.5 7B on this specific cold-start measurement, corresponding to about a &lt;strong&gt;55.9% reduction in mean cold-start time&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This makes the paired correction result more practically interesting: Qwen3 4B preserved the same complete-case outcome as Qwen2.5 7B across all 20 cases while responding substantially faster in the tested local setup.&lt;/p&gt;
&lt;p&gt;Qwen2.5 7B and Qwen3 8B, by contrast, were much closer in execution time. Qwen2.5 7B averaged 54.37 s versus 60.24 s for Qwen3 8B, making the 7B model about &lt;strong&gt;9.7% faster&lt;/strong&gt; on mean cold-start time in this run.&lt;/p&gt;
&lt;p&gt;These timing results should be interpreted within the tested configuration. They describe cold-start execution time on this local Windows/Ollama setup, not a universal model-speed ranking.&lt;/p&gt;
&lt;h4 id=&quot;error-localization-and-explanation-checks&quot;&gt;Error localization and explanation checks&lt;/h4&gt;
&lt;p&gt;Complete-case correction does not capture every difference between the models.&lt;/p&gt;
&lt;p&gt;Across the 21 expected error regions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 20/21 regions localized — &lt;strong&gt;97.6% F1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen2.5 7B:&lt;/strong&gt; 20/21 regions localized — &lt;strong&gt;97.6% F1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 21/21 regions localized — &lt;strong&gt;100% F1&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither Qwen3 4B nor Qwen2.5 7B produced a false-positive error region in this run, while Qwen3 8B localized all 21 expected regions with no false positive.&lt;/p&gt;
&lt;p&gt;Expected-correction coverage followed a similar pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 19/21 expected corrections — 90.5%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen2.5 7B:&lt;/strong&gt; 19/21 expected corrections — 90.5%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 20/21 expected corrections — 95.2%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So Qwen2.5 7B and Qwen3 4B again matched on both correction coverage and error-localization F1, while Qwen3 8B retained a small advantage on these measures.&lt;/p&gt;
&lt;p&gt;The explanation checks showed a more nuanced difference.&lt;/p&gt;
&lt;p&gt;For explanation-language compliance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen2.5 7B:&lt;/strong&gt; 40/40 evaluated fields passed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 41/41 evaluated fields passed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 37 passes and 3 warnings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All three models nevertheless produced parseable JSON, usable core output and full-contract-compliant output in all 20 cases.&lt;/p&gt;
&lt;p&gt;The configured fact-preservation check also passed all 20 cases for every model.&lt;/p&gt;
&lt;p&gt;Among the 16 error-containing cases, the action–explanation consistency check recorded 16 consistent results for each model, with no contradictory, ambiguous or missing entries.&lt;/p&gt;
&lt;p&gt;These automatic checks should not be interpreted as a complete measure of pedagogical quality. They capture selected properties such as language compliance, output discipline and consistency, while deeper explanation quality would require additional linguistic or human evaluation.&lt;/p&gt;
&lt;h3 id=&quot;what-this-means-for-local-writing-assistants&quot;&gt;What this means for local writing assistants&lt;/h3&gt;
&lt;p&gt;The main takeaway is not that one model is universally better than the others.&lt;/p&gt;
&lt;p&gt;In this benchmark, Qwen2.5 7B and Qwen3 4B produced the same complete-case outcome on all 20 paired cases, while Qwen3 4B required substantially less cold-start time in the tested local setup.&lt;/p&gt;
&lt;p&gt;Qwen3 8B, however, completed one additional case and reached 100% error-localization F1.&lt;/p&gt;
&lt;p&gt;This creates a practical trade-off rather than a single winner.&lt;/p&gt;
&lt;p&gt;For this specific workload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B&lt;/strong&gt; offered the strongest efficiency result: the same paired complete-case outcome as Qwen2.5 7B, with much lower measured cold-start time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen2.5 7B&lt;/strong&gt; remained a credible previous-generation baseline: it matched Qwen3 4B on complete cases, expected-correction coverage and error-localization F1, while showing cleaner explanation-language compliance in this run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B&lt;/strong&gt; provided the strongest correction coverage: one additional complete case, one additional expected correction and full error localization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This suggests that choosing a local writing model should be treated as a multi-criteria decision rather than as a simple parameter-count comparison.&lt;/p&gt;
&lt;p&gt;For an interactive writing assistant, the relevant trade-off may involve correction reliability, explanation behavior, latency, model size and the available hardware.&lt;/p&gt;
&lt;p&gt;In this run, Qwen3 8B provided a small measurable correction advantage, but that advantage came with substantially higher cold-start time than Qwen3 4B.&lt;/p&gt;
&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;This benchmark is exploratory and should be interpreted within its experimental scope.&lt;/p&gt;
&lt;p&gt;Several limitations are important:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;20 paired cases:&lt;/strong&gt; the dataset is intentionally focused and is not large enough to support a universal ranking of the models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One language scenario:&lt;/strong&gt; the benchmark evaluates English writing correction with explanations requested in French. Results may differ for other languages or writing tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One model family:&lt;/strong&gt; the comparison is limited to Qwen2.5 7B, Qwen3 4B and Qwen3 8B and should not be generalized to unrelated local model families.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One local machine:&lt;/strong&gt; execution time depends on hardware, background workload, model format, quantization and Ollama configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold-start timing:&lt;/strong&gt; the reported measurements describe this specific cold-start workflow rather than warm-start interaction latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reference-based evaluation:&lt;/strong&gt; outputs were compared against the same frozen, versioned correction reference and acceptance rules across all three models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic explanation checks:&lt;/strong&gt; language compliance, output discipline and consistency were evaluated automatically, but these checks do not constitute a complete linguistic or pedagogical assessment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No direct resource measurement:&lt;/strong&gt; RAM, VRAM, CPU load and energy consumption were not measured in this run.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last point is particularly important for interpreting the efficiency result.&lt;/p&gt;
&lt;p&gt;Qwen3 4B has a substantially smaller nominal parameter count than Qwen2.5 7B, but parameter count alone should not be treated as a direct measurement of hardware-resource usage.&lt;/p&gt;
&lt;p&gt;The benchmark therefore supports a measured latency comparison, not a claim that Qwen3 4B used a specific percentage less RAM, CPU or energy.&lt;/p&gt;
&lt;h3 id=&quot;what-i-plan-to-test-next&quot;&gt;What I plan to test next&lt;/h3&gt;
&lt;p&gt;This second benchmark leaves several useful directions open for further evaluation.&lt;/p&gt;
&lt;p&gt;The next iterations could extend the framework in four directions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;measure &lt;strong&gt;peak RAM and VRAM usage&lt;/strong&gt; alongside correction quality;&lt;/li&gt;
&lt;li&gt;compare &lt;strong&gt;cold-start and warm-start latency&lt;/strong&gt; under the same local conditions;&lt;/li&gt;
&lt;li&gt;extend the benchmark to a &lt;strong&gt;larger and more diverse writing test set&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;compare additional local model families under the same evaluation protocol.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Direct resource measurements would be particularly useful.&lt;/p&gt;
&lt;p&gt;In this run, Qwen3 4B showed a strong latency advantage over Qwen2.5 7B while preserving the same paired complete-case outcome, but memory and compute usage were not measured directly.&lt;/p&gt;
&lt;p&gt;Adding RAM, VRAM and processor-load measurements would make it possible to evaluate the trade-off more completely across:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;correction reliability;&lt;/li&gt;
&lt;li&gt;explanation behavior;&lt;/li&gt;
&lt;li&gt;latency;&lt;/li&gt;
&lt;li&gt;memory footprint;&lt;/li&gt;
&lt;li&gt;compute load;&lt;/li&gt;
&lt;li&gt;and model size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am particularly interested in whether the efficiency pattern observed here remains stable when the task becomes more linguistically demanding and when resource usage is measured directly.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This experiment did not identify a universal winner, but it revealed a clear practical trade-off.&lt;/p&gt;
&lt;p&gt;For this specific 20-case writing benchmark, Qwen3 4B and Qwen2.5 7B produced the same complete-case outcome on every paired case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;both completed &lt;strong&gt;18/20 cases&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;both applied &lt;strong&gt;19/21 expected corrections&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;both reached &lt;strong&gt;97.6% error-localization F1&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The difference was much larger in local execution time.&lt;/p&gt;
&lt;p&gt;Qwen3 4B averaged &lt;strong&gt;23.99 seconds&lt;/strong&gt; of total cold-start time, compared with &lt;strong&gt;54.37 seconds&lt;/strong&gt; for Qwen2.5 7B.&lt;/p&gt;
&lt;p&gt;In this tested configuration, that corresponds to approximately &lt;strong&gt;2.27× faster cold-start execution&lt;/strong&gt; for Qwen3 4B while preserving the same primary paired correction outcome.&lt;/p&gt;
&lt;p&gt;Qwen3 8B remained slightly stronger on correction coverage, completing &lt;strong&gt;19/20 cases&lt;/strong&gt; and reaching &lt;strong&gt;100% error-localization F1&lt;/strong&gt;, but with a mean cold-start time of &lt;strong&gt;60.24 seconds&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The more useful question, therefore, may not be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Which model has the most parameters?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;but rather:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Which model provides the best balance between correction reliability, explanation behavior, responsiveness and available local hardware for the intended task?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For local interactive applications, model size alone is not enough to answer that question.&lt;/p&gt;
&lt;h4 id=&quot;earlier-benchmark&quot;&gt;Earlier benchmark&lt;/h4&gt;
&lt;p&gt;Before this cross-generation comparison, I tested Qwen3 4B, 8B and 14B on the same 20 writing cases.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://getlinguapilot.com/research/qwen3-writing-coach-benchmark/&quot;&gt;Read the earlier Qwen3 benchmark →&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; AI assistance was used to refine the wording and organization of this article. The benchmark results and measurements reported here come from the documented experimental runs and were reviewed for accuracy before publication.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28819/qwen2-5-7b-vs-qwen3-4b-8b-for-writing-correction-60-local-ollama-responses-on-windows</guid>
<pubDate>Mon, 28 Sep 2026 04:48:41 +0000</pubDate>
</item>
<item>
<title>Qwen3 4B vs 8B vs 14B for Writing Correction: 60 Local Ollama Responses on Windows</title>
<link>https://coderlegion.com/26477/qwen3-4b-vs-8b-vs-14b-for-writing-correction-60-local-ollama-responses-on-windows</link>
<description>&lt;p&gt;I expected the larger Qwen3 model to show a clear advantage for writing correction. In this experiment, it didn't.&lt;/p&gt;
&lt;p&gt;Across 20 paired writing cases, Qwen3 8B and 14B both achieved &lt;strong&gt;19/20 complete-case corrections&lt;/strong&gt;, while Qwen3 4B reached &lt;strong&gt;18/20&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The difference became much more noticeable in local execution time: in the tested cold-start setup, the mean cold-start time for Qwen3 8B was roughly half that of Qwen3 14B.&lt;/p&gt;
&lt;p&gt;That raised a practical question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;For a local writing assistant, how much model size do we actually need?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To explore this more systematically, I built a reproducible benchmark comparing Qwen3 4B, 8B and 14B under the same local conditions with Ollama on Windows.&lt;/p&gt;
&lt;h3 id=&quot;benchmark-setup-1&quot;&gt;Benchmark setup&lt;/h3&gt;
&lt;p&gt;The benchmark uses 20 paired writing cases, including 16 sentences with expected errors and 4 already-correct controls.&lt;/p&gt;
&lt;p&gt;Each case was evaluated with the same reference and the same evaluation framework across three locally hosted Qwen3 models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qwen3:4b-q4_K_M&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qwen3:8b-q4_K_M&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qwen3:14b-q4_K_M&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This produced &lt;strong&gt;60 local responses in total&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The models were executed through &lt;strong&gt;Ollama on Windows&lt;/strong&gt; under the same test conditions. The objective was not to identify a universal “best model,” but to compare several dimensions separately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;complete correction of the expected errors;&lt;/li&gt;
&lt;li&gt;localization of the error regions;&lt;/li&gt;
&lt;li&gt;preservation of the original meaning;&lt;/li&gt;
&lt;li&gt;compliance with the requested output structure;&lt;/li&gt;
&lt;li&gt;quality of the explanations;&lt;/li&gt;
&lt;li&gt;local execution time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the primary metric discussed here, a case was considered complete only when the expected correction requirements for that case were satisfied.&lt;/p&gt;
&lt;h3 id=&quot;results-1&quot;&gt;Results&lt;/h3&gt;
&lt;h4 id=&quot;complete-case-correction-1&quot;&gt;Complete-case correction&lt;/h4&gt;
&lt;p&gt;The primary correction results were close:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 18/20 complete cases — &lt;strong&gt;90%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 19/20 complete cases — &lt;strong&gt;95%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 14B:&lt;/strong&gt; 19/20 complete cases — &lt;strong&gt;95%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On this metric alone, increasing the model size from 8B to 14B did not improve the number of complete cases in this test.&lt;/p&gt;
&lt;h4 id=&quot;local-execution-time-1&quot;&gt;Local execution time&lt;/h4&gt;
&lt;p&gt;The difference was much larger in execution time.&lt;/p&gt;
&lt;p&gt;In the tested cold-start configuration, the mean cold-start times were approximately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B:&lt;/strong&gt; 26.6 s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B:&lt;/strong&gt; 60.0 s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 14B:&lt;/strong&gt; 119.4 s&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, while 8B and 14B achieved the same complete-case score, &lt;strong&gt;Qwen3 8B took about half as long as Qwen3 14B on average in this cold-start setup.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The 4B model was substantially faster again, while still completing 18 of the 20 cases.&lt;/p&gt;
&lt;p&gt;This does not mean that 8B is universally better than 14B. It means that, for this specific writing-correction workload and hardware configuration, the additional model size did not translate into a better primary correction score, while the execution-time cost increased considerably.&lt;/p&gt;
&lt;h3 id=&quot;error-localization-and-explanation-quality&quot;&gt;Error localization and explanation quality&lt;/h3&gt;
&lt;p&gt;Complete-case correction and error localization measure different aspects of model behavior.&lt;/p&gt;
&lt;p&gt;Across the 21 expected error regions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 4B&lt;/strong&gt; missed one expected region, with an error-localization F1 of &lt;strong&gt;97.6%&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 8B&lt;/strong&gt; localized all 21 expected regions, reaching &lt;strong&gt;100% F1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3 14B&lt;/strong&gt; missed one expected region and reported one false-positive region, resulting in &lt;strong&gt;95.2% F1&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This distinction matters for a writing coach: producing an acceptable corrected sentence does not necessarily mean that the model identified the underlying error with the same precision.&lt;/p&gt;
&lt;p&gt;Explanation quality was therefore evaluated separately from correction success.&lt;/p&gt;
&lt;p&gt;In this run, &lt;strong&gt;all 41 evaluated explanation fields produced by Qwen3 8B passed the language check&lt;/strong&gt;. Qwen3 4B showed more explanation-language and terminology issues.&lt;/p&gt;
&lt;p&gt;These automatic checks should not be interpreted as a complete measure of pedagogical quality. They cover selected language patterns and contradictions, while deeper explanation quality remains a separate area for linguistic review.&lt;/p&gt;
&lt;h3 id=&quot;what-this-means-for-local-writing-assistants-1&quot;&gt;What this means for local writing assistants&lt;/h3&gt;
&lt;p&gt;The main takeaway is not that one model is universally better than another.&lt;/p&gt;
&lt;p&gt;In this benchmark, the three Qwen3 models produced relatively similar complete-case correction results, but their local execution times differed much more substantially.&lt;/p&gt;
&lt;p&gt;For this specific workload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;4B&lt;/strong&gt; offered the lowest execution time, with a small reduction in correction coverage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8B&lt;/strong&gt; matched 14B on complete-case correction and achieved the strongest error-localization result in this run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14B&lt;/strong&gt; did not improve the primary correction score over 8B, while requiring substantially more execution time in the tested configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This suggests that model selection for a local writing assistant should be treated as a &lt;strong&gt;multi-criteria decision&lt;/strong&gt;, rather than assuming that a larger model will automatically provide the best practical experience.&lt;/p&gt;
&lt;p&gt;Depending on the use case and available hardware, the relevant trade-off may involve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;correction reliability;&lt;/li&gt;
&lt;li&gt;explanation quality;&lt;/li&gt;
&lt;li&gt;latency;&lt;/li&gt;
&lt;li&gt;memory and compute requirements;&lt;/li&gt;
&lt;li&gt;and the expected frequency of use.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a writing coach in particular, responsiveness also matters. A model that is slightly larger or theoretically more capable may not necessarily provide the best user experience if each interaction becomes significantly slower.&lt;/p&gt;
&lt;h3 id=&quot;limitations-1&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;This benchmark is exploratory and should be interpreted within its experimental scope.&lt;/p&gt;
&lt;p&gt;Several limitations are important:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;20 paired cases:&lt;/strong&gt; the dataset is intentionally focused and is not large enough to support a universal ranking of the models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One language scenario:&lt;/strong&gt; the benchmark evaluates English writing correction with explanations requested in French. Results may differ for other languages or writing tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One model family:&lt;/strong&gt; only Qwen3 4B, 8B and 14B were compared. The results should not be generalized to other local model families.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One local machine:&lt;/strong&gt; execution time depends on hardware, model quantization, available memory and Ollama configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold-start timing:&lt;/strong&gt; Ollama was configured with &lt;code&gt;keep_alive=0&lt;/code&gt;, meaning the model was reloaded for each response. The reported timings therefore represent this specific cold-start workflow rather than warm-start interaction latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reference-based evaluation:&lt;/strong&gt; outputs were compared against a frozen, versioned correction reference prepared for this study. This improves consistency across models, but it does not capture every possible acceptable linguistic formulation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explanation-quality checks:&lt;/strong&gt; automatic checks cover selected language patterns and contradictions. They should not be treated as a complete assessment of pedagogical or linguistic quality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These limitations are also why the benchmark is presented as a controlled comparison rather than a claim about the absolute capabilities of each model.&lt;/p&gt;
&lt;h3 id=&quot;what-i-plan-to-test-next-1&quot;&gt;What I plan to test next&lt;/h3&gt;
&lt;p&gt;This first benchmark leaves several useful directions open for further evaluation.&lt;/p&gt;
&lt;p&gt;The next iterations could extend the framework in four directions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;compare additional local model families under the same evaluation protocol;&lt;/li&gt;
&lt;li&gt;examine explanation quality with deeper linguistic and human review;&lt;/li&gt;
&lt;li&gt;compare cold-start and warm-start performance;&lt;/li&gt;
&lt;li&gt;evaluate the same workflow on more constrained hardware.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A larger and more diverse test set would also help determine whether the patterns observed here remain stable beyond these 20 cases.&lt;/p&gt;
&lt;p&gt;I am particularly interested in whether the 8B/14B trade-off observed in this run persists when the task becomes more linguistically demanding.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This experiment did not produce a universal winner, but it did highlight an important practical point: &lt;strong&gt;model size alone is not a sufficient criterion for choosing a local writing model&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For this specific workload, Qwen3 8B matched 14B on complete-case correction while its mean cold-start time was roughly half that of 14B in the tested configuration.&lt;/p&gt;
&lt;p&gt;The more useful question, therefore, may not be “Which model is the largest?” but rather:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Which model provides the best balance between correction quality, explanation quality, responsiveness, and available hardware for the intended use case?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you use local LLMs for writing assistance or other interactive tasks, I would be interested to know which trade-off matters most in your own setup: &lt;strong&gt;quality, latency, memory requirements, or something else?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;follow-up-benchmark&quot;&gt;Follow-up benchmark&lt;/h3&gt;
&lt;p&gt;I later extended this evaluation by comparing Qwen2.5 7B with Qwen3 4B and Qwen3 8B under the same 20-case writing-coach framework.&lt;/p&gt;
&lt;p&gt;In that follow-up, Qwen3 4B matched Qwen2.5 7B on every paired complete-case outcome while averaging &lt;strong&gt;23.99 s&lt;/strong&gt; of cold-start time versus &lt;strong&gt;54.37 s&lt;/strong&gt; for Qwen2.5 7B.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://getlinguapilot.com/research/qwen2-5-vs-qwen3-writing-coach-benchmark/&quot;&gt;Read the follow-up benchmark →&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Disclosure: AI assistance was used to refine the wording and organization of this article. The benchmark results and measurements reported here come from the documented experimental runs and were reviewed for accuracy before publication.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26477/qwen3-4b-vs-8b-vs-14b-for-writing-correction-60-local-ollama-responses-on-windows</guid>
<pubDate>Tue, 08 Sep 2026 10:25:29 +0000</pubDate>
</item>
<item>
<title>How I Connected a Dockerized Python API to a Local Mistral Model</title>
<link>https://coderlegion.com/26086/how-i-connected-a-dockerized-python-api-to-a-local-mistral-model</link>
<description>&lt;h2 id=&quot;how-i-connected-a-dockerized-python-api-to-a-local-mistral-model&quot;&gt;How I Connected a Dockerized Python API to a Local Mistral Model&lt;/h2&gt;
&lt;p&gt;I’m currently building the AI foundation for &lt;strong&gt;MyZubster&lt;/strong&gt;, and one of my first goals was to create a local development environment that could run without relying on paid external AI APIs.&lt;/p&gt;
&lt;p&gt;The stack uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python and Flask for the application API&lt;/li&gt;
&lt;li&gt;Docker Compose for orchestration&lt;/li&gt;
&lt;li&gt;Ollama for local model inference&lt;/li&gt;
&lt;li&gt;Mistral as the language model&lt;/li&gt;
&lt;li&gt;Qdrant for vector storage&lt;/li&gt;
&lt;li&gt;Open WebUI for manual testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post explains how the services are connected and highlights a few issues I encountered along the way.&lt;/p&gt;
&lt;h3 id=&quot;the-architecture&quot;&gt;The architecture&lt;/h3&gt;
&lt;p&gt;The planned AI workflow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User request
    ↓
MyZubster Flask API
    ↓
Semantic search in Qdrant
    ↓
Relevant MyZubster data
    ↓
Mistral through Ollama
    ↓
Generated response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qdrant will provide the retrieval part of the RAG pipeline, while Mistral will generate an answer using the retrieved context.&lt;/p&gt;
&lt;h3 id=&quot;why-ollama-runs-on-the-host&quot;&gt;Why Ollama runs on the host&lt;/h3&gt;
&lt;p&gt;Ollama was already installed and running directly on Windows at:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;mistral:latest&lt;/code&gt; model was also already downloaded.&lt;/p&gt;
&lt;p&gt;I decided not to add another Ollama container because that would duplicate model storage, increase memory usage and conflict with the port already occupied by the host process.&lt;/p&gt;
&lt;p&gt;Docker Desktop provides the special hostname:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;host.docker.internal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Containers can use it to connect to services running on the host machine.&lt;/p&gt;
&lt;p&gt;Inside the MyZubster API container, the Ollama address therefore becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://host.docker.internal:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;verifying-the-connection&quot;&gt;Verifying the connection&lt;/h3&gt;
&lt;p&gt;Before changing the application, I tested the connection using a temporary curl container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker run --rm curlimages/curl:8.12.1 `
  http://host.docker.internal:11434/api/tags
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ollama returned the installed model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;models&quot;: [
    {
      &quot;name&quot;: &quot;mistral:latest&quot;,
      &quot;parameter_size&quot;: &quot;7.2B&quot;,
      &quot;quantization_level&quot;: &quot;Q4_K_M&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that Docker could communicate with Ollama on the Windows host.&lt;/p&gt;
&lt;h3 id=&quot;docker-compose-stack&quot;&gt;Docker Compose stack&lt;/h3&gt;
&lt;p&gt;Here is the Compose configuration used for the local environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  api:
    build:
      context: .
    init: true
    ports:
      - &quot;5000:5000&quot;
    environment:
      MYZUBSTER_HOST: &quot;0.0.0.0&quot;
      MYZUBSTER_PORT: &quot;5000&quot;
      MYZUBSTER_OBSERVATIONS_FILE: &quot;/data/observations.json&quot;
      OLLAMA_BASE_URL: &quot;http://host.docker.internal:11434&quot;
      OLLAMA_MODEL: &quot;mistral:latest&quot;
      QDRANT_URL: &quot;http://qdrant:6333&quot;
      QDRANT_COLLECTION: &quot;myzubster&quot;
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    volumes:
      - observations-data:/data
    depends_on:
      qdrant:
        condition: service_started
    restart: unless-stopped

  qdrant:
    image: qdrant/qdrant:latest
    ports:
      - &quot;127.0.0.1:6333:6333&quot;
      - &quot;127.0.0.1:6334:6334&quot;
    volumes:
      - qdrant-data:/qdrant/storage
    restart: unless-stopped

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    ports:
      - &quot;127.0.0.1:3000:8080&quot;
    environment:
      OLLAMA_BASE_URL: &quot;http://host.docker.internal:11434&quot;
      WEBUI_AUTH: &quot;true&quot;
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    volumes:
      - open-webui-data:/app/backend/data
    restart: unless-stopped

volumes:
  observations-data:
  qdrant-data:
  open-webui-data:

networks:
  default:
    name: myzubster-ai-network
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API communicates with Qdrant using its Compose service name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://qdrant:6333
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It communicates with Ollama through the host gateway:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://host.docker.internal:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;starting-the-environment&quot;&gt;Starting the environment&lt;/h3&gt;
&lt;p&gt;I first validate the Compose file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I pull the external images and start the stack:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose pull
docker compose down
docker compose up -d --build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service status can be checked with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Useful logs are available through:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose logs --tail 100 api
docker compose logs --tail 100 qdrant
docker compose logs --tail 100 open-webui
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;testing-ollama-from-the-application-container&quot;&gt;Testing Ollama from the application container&lt;/h3&gt;
&lt;p&gt;A successful request from Windows does not necessarily prove that the application container can reach Ollama.&lt;/p&gt;
&lt;p&gt;I tested the full connection from inside the API container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose exec api python -c &quot;import json,os,urllib.request; url=os.environ['OLLAMA_BASE_URL']+'/api/generate'; data=json.dumps({'model':os.environ['OLLAMA_MODEL'],'prompt':'Reply only with: MyZubster connected','stream':False}).encode(); req=urllib.request.Request(url,data=data,headers={'Content-Type':'application/json'}); result=json.loads(urllib.request.urlopen(req,timeout=120).read()); print(result['response'].strip())&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The container received a generated response, confirming that the networking and environment variables were configured correctly.&lt;/p&gt;
&lt;h3 id=&quot;a-powershell-trap&quot;&gt;A PowerShell trap&lt;/h3&gt;
&lt;p&gt;One mistake I made was pasting YAML directly into PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  api:
    build:
      context: .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PowerShell interpreted each YAML key as a command and returned errors such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;The term 'services:' is not recognized as the name of a cmdlet
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A Compose configuration must be saved as &lt;code&gt;docker-compose.yml&lt;/code&gt;; it cannot be executed directly.&lt;/p&gt;
&lt;p&gt;To open the file in Visual Studio Code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;code docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always validate it afterward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose config
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;available-services&quot;&gt;Available services&lt;/h3&gt;
&lt;p&gt;After startup, the local tools are available at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MyZubster API: &lt;code&gt;http://localhost:5000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open WebUI: &lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Qdrant dashboard: &lt;code&gt;http://localhost:6333/dashboard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ollama API: &lt;code&gt;http://localhost:11434&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qdrant and Open WebUI are bound to &lt;code&gt;127.0.0.1&lt;/code&gt;, so their ports are not exposed to other devices on the network.&lt;/p&gt;
&lt;h3 id=&quot;what-comes-next&quot;&gt;What comes next&lt;/h3&gt;
&lt;p&gt;The containers provide the infrastructure, but the actual RAG pipeline still needs to be implemented.&lt;/p&gt;
&lt;p&gt;The next development steps are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install an embedding model in Ollama.&lt;/li&gt;
&lt;li&gt;Create a Qdrant collection with the correct vector dimensions.&lt;/li&gt;
&lt;li&gt;Generate embeddings for MyZubster content.&lt;/li&gt;
&lt;li&gt;Store vectors together with useful metadata.&lt;/li&gt;
&lt;li&gt;Embed incoming user questions.&lt;/li&gt;
&lt;li&gt;Retrieve the most relevant records from Qdrant.&lt;/li&gt;
&lt;li&gt;Add the retrieved context to the Mistral prompt.&lt;/li&gt;
&lt;li&gt;Expose the workflow through an API endpoint.&lt;/li&gt;
&lt;li&gt;Add integration tests and response validation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One possible endpoint will be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST /api/ai/ask
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;question&quot;: &quot;Which products use sustainable packaging?&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application will search its own indexed data and ask Mistral to generate an answer grounded in those results.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;This setup creates a practical local AI development environment while keeping data and model inference on the developer’s machine.&lt;/p&gt;
&lt;p&gt;The most important lesson was to verify each connection independently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Host to Ollama&lt;/li&gt;
&lt;li&gt;Docker to Ollama&lt;/li&gt;
&lt;li&gt;API to Qdrant&lt;/li&gt;
&lt;li&gt;Browser to Open WebUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those connections work, debugging the application layer becomes much easier.&lt;/p&gt;
&lt;p&gt;The MyZubster AI integration is still evolving, but the infrastructure is now ready for the next phase: building and testing the RAG workflow.&lt;/p&gt;
&lt;p&gt;What local model and vector database are you using for your AI projects?&lt;/p&gt;
&lt;h2 id=&quot;docker-artificialintelligence-python-ollama-mistral-qdrant-rag&quot;&gt;Docker #ArtificialIntelligence #Python #Ollama #Mistral #Qdrant #RAG&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26086/how-i-connected-a-dockerized-python-api-to-a-local-mistral-model</guid>
<pubDate>Wed, 02 Sep 2026 17:04:05 +0000</pubDate>
</item>
<item>
<title>How to Use Qwen 3 with VS Code (FREE) | The Best QWEN 3 Setup with Ollama?</title>
<link>https://coderlegion.com/24025/how-to-use-qwen-3-with-vs-code-free-the-best-qwen-3-setup-with-ollama</link>
<description>&lt;p&gt;In this step-by-step tutorial, I'll show you how to Use Qwen 3 with VS Code using Ollama and turn your computer into a FREE local AI coding assistant for free.&lt;/p&gt;
&lt;p&gt;Whether you're a beginner or an experienced developer, this guide will help you install everything from scratch, configure VS Code, fix common setup issues, and generate your first piece of code with QWEN 3.&lt;/p&gt;
&lt;p&gt;🔥 In This Video:&lt;br&gt;
✅ What is Qwen 3?&lt;br&gt;
✅ Why use Qwen 3 for coding?&lt;br&gt;
✅ Install Ollama&lt;br&gt;
✅ Download and run Qwen 3 locally&lt;br&gt;
✅ Connect Qwen 3 with VS Code&lt;br&gt;
✅ Generate your first code&lt;br&gt;
✅ Common setup mistakes and fixes&lt;/p&gt;
&lt;p&gt;🎯 This is Part 1 of the series.&lt;/p&gt;
&lt;p&gt;In Part 2, we'll use the same setup to build a complete React GitHub Profile Finder application using Qwen 3 step by step.&lt;/p&gt;
&lt;p&gt;👇 Resources&lt;/p&gt;
&lt;p&gt;• Ollama&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://ollama.com&quot;&gt;https://ollama.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;• Qwen 3 Model&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://ollama.com/library/qwen3&quot;&gt;https://ollama.com/library/qwen3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;💻 Tech Used&lt;br&gt;
• VS Code&lt;br&gt;
• Qwen 3&lt;br&gt;
• Ollama&lt;/p&gt;
&lt;p&gt;Join our growing community of learners and developers. Don't forget to like, comment, and subscribe to stay updated with the latest tutorials, tips, and projects.&lt;/p&gt;
&lt;p&gt;Connect with us:&lt;/p&gt;
&lt;p&gt;Subscribe: &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtube.com/@TheCodeStreet&quot;&gt;https://youtube.com/@TheCodeStreet&lt;/a&gt;&lt;br&gt;
Join our Facebook Community: &lt;a rel=&quot;nofollow&quot; href=&quot;https://facebook.com/TheCodeStreet&quot;&gt;https://facebook.com/TheCodeStreet&lt;/a&gt;&lt;br&gt;
Visit our website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://thecodestreet.dev&quot;&gt;https://thecodestreet.dev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Get vidIQ to grow your channel faster! 🚀&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://vidiq.com/TheCodeStreet&quot;&gt;https://vidiq.com/TheCodeStreet&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Together, let's code the future at CodeStreet! 💡&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/24025/how-to-use-qwen-3-with-vs-code-free-the-best-qwen-3-setup-with-ollama</guid>
<pubDate>Mon, 03 Aug 2026 07:13:42 +0000</pubDate>
</item>
<item>
<title>Image Generation with Ollama is back with Japanese, Korean and Chinese Languages  Support!</title>
<link>https://coderlegion.com/15586/image-generation-with-ollama-is-back-with-japanese-korean-and-chinese-languages-support</link>
<description>&lt;p&gt;An international collaborative work &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hz45saspaz1xcdy2lzu.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;At the start of the year, I developed an image generation application using Bob to build a custom interface. The backend integrates the ‘&lt;strong&gt;x/flux2-klein&lt;/strong&gt;’ and ‘&lt;strong&gt;x/z-image-turbo&lt;/strong&gt;’ models alongside Ollama. Recently, the application received significant enhancements thanks to contributions from my colleague, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/oniak3/&quot;&gt;Akira Onishi&lt;/a&gt;-san, in Japan.&lt;/p&gt;
&lt;p&gt;What’s truly fantastic is the way my colleague transformed the app; by leveraging &lt;strong&gt;Bob&lt;/strong&gt;, he made the architecture modular and much more secure. He also went the extra mile by refining the &lt;strong&gt;bash scripts&lt;/strong&gt;, making the automatic startup and shutdown processes more robust than ever.&lt;/p&gt;
&lt;p&gt;So let’s dig into the application’s enhancements.&lt;/p&gt;
&lt;h3 id=&quot;implementation&quot;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6izbv22k15wnc1ju2998.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The architecture of the application remains the same, but the main enhanced modules are provided hereafter (the change log is accessible on GitHub)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;app.js&lt;/strong&gt;: The latest update to &lt;code&gt;app.js&lt;/code&gt; introduces significant hardening and performance tracking. On the security front, we’ve eliminated vulnerabilities by replacing &lt;code&gt;innerHTML&lt;/code&gt; with &lt;strong&gt;safe DOM&lt;/strong&gt; manipulation and implementing &lt;strong&gt;filename sanitization&lt;/strong&gt; to prevent directory traversal. Additionally, we’ve integrated &lt;strong&gt;memory usage tracking&lt;/strong&gt; within the history logs and refined the &lt;strong&gt;IME composition handling&lt;/strong&gt; to ensure a seamless experience for Japanese input.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Application state
let generationHistory = [];

// DOM elements
const promptInput = document.getElementById('prompt');
const modelSelect = document.getElementById('model');
const generateBtn = document.getElementById('generateBtn');
const resultContainer = document.getElementById('result');
const statusDiv = document.getElementById('status');
const historyList = document.getElementById('historyList');
const btnText = generateBtn.querySelector('.btn-text');
const loader = generateBtn.querySelector('.loader');

// Check Ollama connection on load
checkOllamaConnection();

// Event listeners
generateBtn.addEventListener('click', generateImage);

// Check if Ollama is running
async function checkOllamaConnection() {
    statusDiv.textContent = 'Checking connection...';
    statusDiv.className = 'status checking';
    
    try {
        const response = await fetch('/api/health');
        const data = await response.json();
        
        if (data.status === 'ok') {
            statusDiv.textContent = '✓ Connected to Ollama';
            statusDiv.className = 'status connected';
        } else {
            statusDiv.textContent = '✗ Ollama not connected';
            statusDiv.className = 'status disconnected';
        }
    } catch (error) {
        statusDiv.textContent = '✗ Cannot connect to server';
        statusDiv.className = 'status disconnected';
    }
}

// Generate image function
async function generateImage() {
    const prompt = promptInput.value.trim();
    const model = modelSelect.value;
    
    if (!prompt) {
        alert('Please enter a prompt');
        return;
    }
    
    // Disable button and show loading state
    generateBtn.disabled = true;
    btnText.textContent = 'Generating...';
    loader.style.display = 'block';
    
    // Clear previous result
    resultContainer.innerHTML = '';
    const placeholder = document.createElement('div');
    placeholder.className = 'placeholder';
    const p = document.createElement('p');
    p.textContent = 'Generating your image...';
    placeholder.appendChild(p);
    resultContainer.appendChild(placeholder);
    
    try {
        const response = await fetch('/api/generate', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ prompt, model })
        });
        
        const data = await response.json();
        
        if (data.success) {
            displayResult(data, prompt, model);
            addToHistory(prompt, model, data.result, data.memoryUsage);
        } else {
            displayError(data.error || 'Failed to generate image');
        }
    } catch (error) {
        displayError('Network error: ' + error.message);
    } finally {
        // Re-enable button
        generateBtn.disabled = false;
        btnText.textContent = 'Generate Image';
        loader.style.display = 'none';
    }
}

// Display the result
function displayResult(data, prompt, model) {
    const resultContent = document.createElement('div');
    resultContent.className = 'result-content';
    
    // Check if result contains image data (base64)
    if (data.result &amp;amp;&amp;amp; (data.result.startsWith('data:image') || data.result.includes('base64'))) {
        const img = document.createElement('img');
        img.src = data.result;
        img.alt = 'Generated image';
        img.className = 'result-image';
        resultContent.appendChild(img);
        
        // Add download button
        const downloadBtn = document.createElement('button');
        downloadBtn.className = 'download-btn';
        downloadBtn.textContent = '⬇️ Download Image';
        downloadBtn.onclick = () =&amp;gt; downloadImage(data.result, prompt);
        resultContent.appendChild(downloadBtn);
    } else {
        // Display as text if not an image
        const textDiv = document.createElement('div');
        textDiv.className = 'result-text';
        textDiv.textContent = data.result;
        resultContent.appendChild(textDiv);
    }
    
    // Add info section
    const infoDiv = document.createElement('div');
    infoDiv.className = 'result-info';
    
    const modelLabel = document.createElement('strong');
    modelLabel.textContent = 'Model:';
    infoDiv.appendChild(modelLabel);
    infoDiv.appendChild(document.createTextNode(' ' + model));
    infoDiv.appendChild(document.createElement('br'));
    
    const promptLabel = document.createElement('strong');
    promptLabel.textContent = 'Prompt:';
    infoDiv.appendChild(promptLabel);
    infoDiv.appendChild(document.createTextNode(' ' + prompt));
    
    resultContent.appendChild(infoDiv);
    
    resultContainer.innerHTML = '';
    resultContainer.appendChild(resultContent);
}

// Download image with custom filename
function downloadImage(imageDataUrl, defaultPrompt) {
    // Generate a default filename from the prompt (sanitized)
    const sanitizedPrompt = defaultPrompt
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, '')
        .replace(/\s+/g, '-')
        .substring(0, 50);
    
    const timestamp = new Date().toISOString().split('T')[0];
    const defaultFilename = `${sanitizedPrompt}-${timestamp}.png`;
    
    // Prompt user for filename
    const filename = prompt('Enter filename for the image:', defaultFilename);
    
    // If user cancels, don't download
    if (!filename) return;
    
    // Remove all path separators to prevent directory traversal
    const sanitizedFilename = filename.replace(/[^a-zA-Z0-9._-]/g, '_');
    
    // Ensure filename has .png extension
    const finalFilename = sanitizedFilename.endsWith('.png') ? sanitizedFilename : `${sanitizedFilename}.png`;
    
    // Create a temporary link element and trigger download
    const link = document.createElement('a');
    link.href = imageDataUrl;
    link.download = finalFilename;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

// Display error message
function displayError(message) {
    const errorDiv = document.createElement('div');
    errorDiv.className = 'error-message';
    
    const strong = document.createElement('strong');
    strong.textContent = 'Error:';
    
    const messageText = document.createTextNode(' ' + message);
    
    const br1 = document.createElement('br');
    const br2 = document.createElement('br');
    
    const small = document.createElement('small');
    small.textContent = 'Make sure Ollama is running and the models are installed.';
    
    errorDiv.appendChild(strong);
    errorDiv.appendChild(messageText);
    errorDiv.appendChild(br1);
    errorDiv.appendChild(br2);
    errorDiv.appendChild(small);
    
    resultContainer.innerHTML = '';
    resultContainer.appendChild(errorDiv);
}

// Add to history
function addToHistory(prompt, model, result, memoryUsage) {
    const historyItem = {
        prompt,
        model,
        result,
        timestamp: new Date().toLocaleString(),
        memoryUsage: memoryUsage || null
    };
    
    generationHistory.unshift(historyItem);
    
    // Keep only last 10 items
    if (generationHistory.length &amp;gt; 10) {
        generationHistory.pop();
    }
    
    updateHistoryDisplay();
}

// Update history display
function updateHistoryDisplay() {
    historyList.innerHTML = '';
    
    if (generationHistory.length === 0) {
        const emptyMessage = document.createElement('p');
        emptyMessage.style.color = '#9ca3af';
        emptyMessage.style.textAlign = 'center';
        emptyMessage.textContent = 'No generation history yet';
        historyList.appendChild(emptyMessage);
        return;
    }
    
    generationHistory.forEach((item, index) =&amp;gt; {
        const historyItemDiv = document.createElement('div');
        historyItemDiv.className = 'history-item';
        
        const promptDiv = document.createElement('div');
        promptDiv.className = 'history-item-prompt';
        promptDiv.textContent = item.prompt;
        historyItemDiv.appendChild(promptDiv);
        
        const modelDiv = document.createElement('div');
        modelDiv.className = 'history-item-model';
        modelDiv.textContent = item.model;
        historyItemDiv.appendChild(modelDiv);
        
        const timeDiv = document.createElement('div');
        timeDiv.className = 'history-item-time';
        timeDiv.textContent = item.timestamp;
        historyItemDiv.appendChild(timeDiv);
        
        if (item.memoryUsage) {
            const memoryDiv = document.createElement('div');
            memoryDiv.className = 'history-item-memory';
            memoryDiv.textContent = `Memory: RSS +${item.memoryUsage.rssDiff}MB, Heap +${item.memoryUsage.heapUsedDiff}MB`;
            historyItemDiv.appendChild(memoryDiv);
        }
        
        historyItemDiv.addEventListener('click', () =&amp;gt; {
            promptInput.value = item.prompt;
            modelSelect.value = item.model;
            displayResult({ result: item.result }, item.prompt, item.model);
        });
        
        historyList.appendChild(historyItemDiv);
    });
}

// Allow Enter key to submit (with Shift+Enter for new line)
// Ignore Enter key during Japanese IME composition
promptInput.addEventListener('keydown', (e) =&amp;gt; {
    if (e.key === 'Enter' &amp;amp;&amp;amp; !e.shiftKey &amp;amp;&amp;amp; !e.isComposing) {
        e.preventDefault();
        generateImage();
    }
});

// Made with Bob
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;server.js&lt;/strong&gt;: The &lt;code&gt;server.js&lt;/code&gt; file has been completely refactored into a &lt;strong&gt;modular, well-documented architecture&lt;/strong&gt; using helper functions. To improve observability, we integrated c&lt;strong&gt;omprehensive memory tracking&lt;/strong&gt; (monitoring RSS, heap, and external usage), alongside centralized configuration constants for easier maintenance. Finally, the service is now more resilient and easier to troubleshoot thanks to &lt;strong&gt;enhanced error handling&lt;/strong&gt;, robust response parsing, and the addition of conditional debug logging.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require('express');
const cors = require('cors');
const axios = require('axios');
const { exec } = require('child_process');
const util = require('util');
const path = require('path');

const execPromise = util.promisify(exec);

const app = express();
const PORT = process.env.PORT || 3000;
const OLLAMA_API_URL = 'http://localhost:11434';
const MAX_CONTENT_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
const IMAGE_GENERATION_TIMEOUT_MS = 180000; // 3 minutes timeout for image generation
const OLLAMA_REQUEST_CONFIG = {
    timeout: IMAGE_GENERATION_TIMEOUT_MS,
    maxContentLength: MAX_CONTENT_SIZE_BYTES,
    maxBodyLength: MAX_CONTENT_SIZE_BYTES
};

app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// ============================================================================
// Helper Functions
// ============================================================================

/**
 * Validate request body for image generation
 */
function validateRequest(body) {
    const { prompt, model } = body;
    if (!prompt || !model) {
        return { valid: false, error: 'Prompt and model are required' };
    }
    return { valid: true, prompt, model };
}

/**
 * Measure current memory usage
 */
function measureMemory() {
    return process.memoryUsage();
}

/**
 * Calculate and log memory usage difference
 */
function logMemoryUsage(memBefore, memAfter) {
    const memDiff = {
        rss: ((memAfter.rss - memBefore.rss) / 1024 / 1024).toFixed(2),
        heapTotal: ((memAfter.heapTotal - memBefore.heapTotal) / 1024 / 1024).toFixed(2),
        heapUsed: ((memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024).toFixed(2),
        external: ((memAfter.external - memBefore.external) / 1024 / 1024).toFixed(2)
    };
    
    debugLog('=== Memory Usage Before Generation ===');
    debugLog(`RSS: ${(memBefore.rss / 1024 / 1024).toFixed(2)} MB`);
    debugLog(`Heap Total: ${(memBefore.heapTotal / 1024 / 1024).toFixed(2)} MB`);
    debugLog(`Heap Used: ${(memBefore.heapUsed / 1024 / 1024).toFixed(2)} MB`);
    debugLog(`External: ${(memBefore.external / 1024 / 1024).toFixed(2)} MB`);
    
    debugLog('=== Memory Usage After Generation ===');
    debugLog(`RSS: ${(memAfter.rss / 1024 / 1024).toFixed(2)} MB (Δ ${memDiff.rss} MB)`);
    debugLog(`Heap Total: ${(memAfter.heapTotal / 1024 / 1024).toFixed(2)} MB (Δ ${memDiff.heapTotal} MB)`);
    debugLog(`Heap Used: ${(memAfter.heapUsed / 1024 / 1024).toFixed(2)} MB (Δ ${memDiff.heapUsed} MB)`);
    debugLog(`External: ${(memAfter.external / 1024 / 1024).toFixed(2)} MB (Δ ${memDiff.external} MB)`);
    
    return memDiff;
}

/**
 * Track memory usage for a generation operation
 */
function trackMemoryUsage(beforeMem) {
    const afterMem = measureMemory();
    return logMemoryUsage(beforeMem, afterMem);
}

/**
 * Conditional debug logging
 */
function debugLog(...args) {
    if (process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true') {
        console.log(...args);
    }
}

/**
 * Extract base64 image data from various response formats
 */
function extractImageData(data) {
    if (data.image) return `data:image/png;base64,${data.image}`;
    if (data.images?.[0]) return `data:image/png;base64,${data.images[0]}`;
    if (Buffer.isBuffer(data)) return `data:image/png;base64,${data.toString('base64')}`;
    return null;
}

/**
 * Parse NDJSON string and return last line as JSON
 */
function parseNDJSON(data) {
    const lines = data.trim().split('\n');
    return JSON.parse(lines[lines.length - 1]);
}

/**
 * Handle string response (NDJSON format)
 */
function parseStringResponse(data, log) {
    log('Response is a string, length:', data.length);
    
    try {
        const parsed = parseNDJSON(data);
        log('Parsed response keys:', Object.keys(parsed));
        
        const imageData = extractImageData(parsed);
        if (imageData) {
            log('✓ Found image in parsed response');
            return { imageData, ollamaResponse: '' };
        }
        
        if (parsed.response) {
            log('Found response field, length:', parsed.response.length);
            return { imageData: null, ollamaResponse: parsed.response };
        }
    } catch (e) {
        log('Failed to parse as JSON:', e.message);
        return { imageData: null, ollamaResponse: data };
    }
    
    return { imageData: null, ollamaResponse: '' };
}

/**
 * Handle object/Buffer response
 */
function parseObjectResponse(data, log) {
    const imageData = extractImageData(data);
    if (imageData) {
        log('✓ Found image in response');
        return { imageData, ollamaResponse: '' };
    }
    
    if (data.response) {
        log('Response length:', data.response.length);
        return { imageData: null, ollamaResponse: data.response };
    }
    
    return { imageData: null, ollamaResponse: '' };
}

/**
 * Parse Ollama API response and extract image/text data
 *
 * CRITICAL: Ollama returns newline-delimited JSON (NDJSON) for image generation
 * Format: Each line is a separate JSON object
 * Example:
 * {&quot;model&quot;:&quot;x/flux2-klein:4b&quot;,&quot;created_at&quot;:&quot;...&quot;,&quot;response&quot;:&quot;&quot;,&quot;done&quot;:false}
 * {&quot;model&quot;:&quot;x/flux2-klein:4b&quot;,&quot;created_at&quot;:&quot;...&quot;,&quot;response&quot;:&quot;&quot;,&quot;done&quot;:false}
 * {&quot;model&quot;:&quot;x/flux2-klein:4b&quot;,&quot;created_at&quot;:&quot;...&quot;,&quot;done&quot;:true,&quot;image&quot;:&quot;base64data...&quot;}
 *
 * The LAST line contains the complete image in the 'image' field (singular, not 'images')
 */
function parseOllamaResponse(data) {
    debugLog('Ollama API response received');
    debugLog('Response data type:', typeof data);
    
    if (typeof data === 'object' &amp;amp;&amp;amp; !Buffer.isBuffer(data)) {
        debugLog('Response data keys:', Object.keys(data));
    }
    
    if (typeof data === 'string') {
        return parseStringResponse(data, debugLog);
    }
    
    if (typeof data === 'object' || Buffer.isBuffer(data)) {
        return parseObjectResponse(data, debugLog);
    }
    
    debugLog('✗ No image or response data found');
    return { imageData: null, ollamaResponse: '' };
}

/**
 * Build API response object
 */
function buildResponse({ imageData, ollamaResponse, model, memDiff, responseData }) {
    return {
        success: true,
        result: imageData || ollamaResponse || 'No image generated',
        model,
        hasImage: !!imageData,
        memoryUsage: {
            rssDiff: memDiff.rss,
            heapUsedDiff: memDiff.heapUsed
        },
        debug: {
            responseType: typeof responseData,
            isBuffer: Buffer.isBuffer(responseData),
            responseLength: ollamaResponse.length,
            hasImages: !!(responseData.images)
        }
    };
}

/**
 * Generate image using Ollama API
 */
async function generateImageWithOllama(model, prompt) {
    return axios.post(
        `${OLLAMA_API_URL}/api/generate`,
        { model, prompt, stream: false },
        OLLAMA_REQUEST_CONFIG
    );
}

/**
 * Handle error responses
 */
function handleError(error, res) {
    console.error('Error generating image:', error.message);
    if (error.response?.data) {
        console.error('Error details:', error.response.data);
    }
    if (error.stack) {
        console.error('Stack trace:', error.stack);
    }
    res.status(500).json({
        error: 'Failed to generate image',
        details: error.message,
        response: error.response?.data || ''
    });
}

// ============================================================================
// API Endpoints
// ============================================================================

/**
 * Endpoint to generate image using Ollama HTTP API
 * This endpoint handles image generation requests from the frontend
 */
app.post('/api/generate', async (req, res) =&amp;gt; {
    const { valid, error, prompt, model } = validateRequest(req.body);
    if (!valid) {
        return res.status(400).json({ error });
    }

    try {
        const memBefore = measureMemory();
        debugLog(`Generating image with model: ${model}`);

        const response = await generateImageWithOllama(model, prompt);
        const { imageData, ollamaResponse } = parseOllamaResponse(response.data);
        const memDiff = trackMemoryUsage(memBefore);
        
        res.json(buildResponse({
            imageData,
            ollamaResponse,
            model,
            memDiff,
            responseData: response.data
        }));

    } catch (error) {
        handleError(error, res);
    }
});

// Endpoint to check available mod
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15586/image-generation-with-ollama-is-back-with-japanese-korean-and-chinese-languages-support</guid>
<pubDate>Thu, 23 Apr 2026 05:55:01 +0000</pubDate>
</item>
<item>
<title>I Ported the Ollama Desktop App to Linux Just in Time for Ubuntu 26.04 LTS</title>
<link>https://coderlegion.com/14525/i-ported-the-ollama-desktop-app-to-linux-just-in-time-for-ubuntu-26-04-lts</link>
<description>&lt;p&gt;Ubuntu 26.04 LTS drops this week. I've been running the RC for months. And I think it's going to be the release that finally pulls a meaningful chunk of people off Windows — especially AI-curious developers who are tired of the telemetry, the forced updates, and the general feeling that Microsoft is treating them like a product.&lt;/p&gt;
&lt;p&gt;So I've been on a mission: take the cool AI tooling that still assumes Windows or macOS, and make it work natively on Linux.&lt;/p&gt;
&lt;p&gt;This week's project: &lt;strong&gt;the official Ollama desktop app&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://ollama.com&quot;&gt;Ollama&lt;/a&gt; is incredible. Local LLMs, dead simple, fast. But their desktop app — the polished GTK-style chat UI with model management, file attachments, settings, chat history — &lt;strong&gt;officially ships for Windows and macOS only&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every single Go file in the &lt;code&gt;app/&lt;/code&gt; directory of &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ollama/ollama&quot;&gt;ollama/ollama&lt;/a&gt; starts with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;//go:build windows || darwin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linux users are left running &lt;code&gt;ollama serve&lt;/code&gt; and pointing a browser at &lt;code&gt;localhost:11434&lt;/code&gt;. It works, but it's not the experience.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-discovery&quot;&gt;The Discovery&lt;/h3&gt;
&lt;p&gt;Before writing a single line of code, I did what any good engineer does: I read the source.&lt;/p&gt;
&lt;p&gt;The Ollama desktop app is built on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/webview/webview&quot;&gt;webview&lt;/a&gt; — a lightweight C++ library that wraps the platform's native browser engine. On Windows it uses WebView2 (Edge). On macOS it uses WebKit. And on Linux?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// From webview.h, line ~400:
#if defined(WEBVIEW_GTK)
// Full GTK3 + WebKit2GTK implementation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Linux support was already there.&lt;/strong&gt; Fully implemented. Sitting in the header file, waiting. The Go build tags were the only thing blocking it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-port&quot;&gt;The Port&lt;/h3&gt;
&lt;p&gt;Six new files. A patch. About 350 lines of Go.&lt;/p&gt;
&lt;h4 id=&quot;1-unlock-the-build-tags&quot;&gt;1. Unlock the build tags&lt;/h4&gt;
&lt;p&gt;~40 files needed one line changed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// before
//go:build windows || darwin

// after  
//go:build windows || darwin || linux
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-wire-up-the-cgo-flags&quot;&gt;2. Wire up the CGo flags&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;app/webview/webview.go&lt;/code&gt; needed Linux-specific build flags to find GTK and WebKit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;#cgo linux CXXFLAGS: -DWEBVIEW_GTK -std=c++14
#cgo linux LDFLAGS: -ldl
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: Ubuntu 26.04 ships &lt;code&gt;webkit2gtk-4.1&lt;/code&gt; — not &lt;code&gt;4.0&lt;/code&gt;. If you're targeting older distros, check your version.&lt;/p&gt;
&lt;h4 id=&quot;3-platform-files&quot;&gt;3. Platform files&lt;/h4&gt;
&lt;p&gt;The app architecture expects platform-specific implementations of a handful of interfaces. I wrote six &lt;code&gt;_linux.go&lt;/code&gt; files:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;app_linux.go&lt;/code&gt;&lt;/strong&gt; — GTK window management via CGo, lockfile-based single-instance enforcement, signal cleanup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func handleExistingInstance(startHidden bool) {
    f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
    if err != nil {
        slog.Info(&quot;another ollama-app instance detected, exiting&quot;)
        os.Exit(0)
    }
    f.Close()
    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
        &amp;lt;-c
        removeLock()
    }()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;server_linux.go&lt;/code&gt;&lt;/strong&gt; — The app normally spawns the Ollama daemon itself. On Linux, systemd handles that. So this file is just a health-checker that pings &lt;code&gt;localhost:11434&lt;/code&gt; every 30 seconds and logs if it's unreachable. Never spawns a process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;updater_linux.go&lt;/code&gt;&lt;/strong&gt; — All no-ops. systemd and apt manage updates on Linux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;filepicker_linux.go&lt;/code&gt;&lt;/strong&gt; — File/folder dialogs via &lt;code&gt;zenity&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func selectDirectory(title string) (string, error) {
    out, err := exec.Command(&quot;zenity&quot;, &quot;--file-selection&quot;, &quot;--directory&quot;, &quot;--title=&quot;+title).Output()
    if err != nil {
        return &quot;&quot;, err
    }
    return strings.TrimSpace(string(out)), nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;dialog/dlgs_linux.go&lt;/code&gt;&lt;/strong&gt; — zenity implementations of the app's dialog interface (yes/no, info, error, file pickers).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;webview_linux.go&lt;/code&gt;&lt;/strong&gt; — The Webview struct without the Windows &lt;code&gt;menu.h&lt;/code&gt; CGo baggage, plus all the JS bindings: &lt;code&gt;ready&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;drag&lt;/code&gt;, &lt;code&gt;doubleClick&lt;/code&gt;, &lt;code&gt;zoomIn&lt;/code&gt;, &lt;code&gt;zoomOut&lt;/code&gt;, &lt;code&gt;selectFiles&lt;/code&gt;, &lt;code&gt;selectWorkingDirectory&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;4-the-one-frontend-bug&quot;&gt;4. The one frontend bug&lt;/h4&gt;
&lt;p&gt;The React sidebar had:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;{isWindows &amp;amp;&amp;amp; (
  &amp;lt;Link href=&quot;/settings&quot;&amp;gt;Settings&amp;lt;/Link&amp;gt;
)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Linux, &lt;code&gt;navigator.platform&lt;/code&gt; returns &lt;code&gt;&quot;Linux x86_64&quot;&lt;/code&gt; — so &lt;code&gt;isWindows&lt;/code&gt; is false and Settings just... doesn't appear. One-line fix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const isMac = navigator.platform.toLowerCase().includes(&quot;mac&quot;);

{!isMac &amp;amp;&amp;amp; (
  &amp;lt;Link href=&quot;/settings&quot;&amp;gt;Settings&amp;lt;/Link&amp;gt;
)}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-result&quot;&gt;The Result&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/johnohhh1/ollama-webchat-ubuntu.git
cd ollama-webchat-ubuntu
bash build.sh
sudo cp build/ollama-app /usr/local/bin/ollama-app
ollama-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A native GTK3 window. WebKit2GTK rendering the full React SPA. Chat history in SQLite. File attachments. Model management. Settings. Dark mode. Wayland-native — no XWayland needed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-nvidia-gotcha&quot;&gt;The NVIDIA gotcha&lt;/h3&gt;
&lt;p&gt;Running an RTX 5070 (Blackwell) with the proprietary driver? You'll see a wall of EGL warnings on startup:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 17: 10de:2f04, driver (null)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Ignore them.&lt;/strong&gt; There's no open-source Mesa driver for Blackwell yet — WebKit probes Mesa EGL, fails, and falls back to Cairo software rendering for the compositor. For a chat UI it's completely imperceptible. Page layout and text were always CPU-rendered anyway.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-ubuntu-26-04-changes-things&quot;&gt;Why Ubuntu 26.04 changes things&lt;/h3&gt;
&lt;p&gt;26.04 LTS is the first Ubuntu release with X11 removed entirely — Wayland only. GNOME 50. The desktop finally feels like a first-class target, not an afterthought.&lt;/p&gt;
&lt;p&gt;At the same time, the local AI tooling ecosystem has exploded. Ollama, LM Studio, Open WebUI, llama.cpp — these tools are genuinely good, and they mostly work on Linux. The desktop app experience is just lagging behind.&lt;/p&gt;
&lt;p&gt;That's the gap I'm trying to close. If you're a developer making the jump from Windows to Ubuntu 26.04 this week, you shouldn't have to give up your tools.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;repo&quot;&gt;Repo&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/johnohhh1/ollama-webchat-ubuntu&quot;&gt;github.com/johnohhh1/ollama-webchat-ubuntu&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PRs welcome — especially if you test on Fedora, Arch, or Ubuntu 24.04. And if you're building other AI tools that are stuck behind Windows/macOS build tags, the pattern here is reusable.&lt;/p&gt;
&lt;p&gt;The C library usually already supports Linux. The build tags are almost always the only thing in the way.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14525/i-ported-the-ollama-desktop-app-to-linux-just-in-time-for-ubuntu-26-04-lts</guid>
<pubDate>Sat, 11 Apr 2026 22:50:53 +0000</pubDate>
</item>
<item>
<title>My Experience Running Ollama Local Models</title>
<link>https://coderlegion.com/7746/my-experience-running-ollama-local-models</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4631340665111585534&quot; alt=&quot;&quot;&gt;&lt;br&gt;
Over the past few weeks, I’ve been experimenting with Ollama to run local models on my machine.&lt;br&gt;
Here’s what I discovered:&lt;/p&gt;
&lt;p&gt;⚡ Performance &amp;amp; Speed  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lightweight models like Gemma 3B and Phi-3 Mini run surprisingly well even on constrained hardware.  &lt;/li&gt;
&lt;li&gt;Caching and modular setup helped me keep inference times low.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;️ Workflow &amp;amp; Setup  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ollama’s model management makes it easy to swap and curate a lean library.  &lt;/li&gt;
&lt;li&gt;I trimmed unused models and focused on a 3-model setup under 2GB each for rapid prototyping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Use Cases  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built a Streamlit chatbot powered by Gemma 3B:1 — smooth local inference with a polished UI.  &lt;/li&gt;
&lt;li&gt;Compared models side by side for reasoning depth vs. speed, which gave me practical insights into trade-offs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Takeaways  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local deployment isn’t just about independence from cloud — it forces you to think strategically about efficiency.  &lt;/li&gt;
&lt;li&gt;Branding, UI polish, and repo structure matter just as much as raw performance when sharing projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’d love to hear how others are balancing &lt;strong&gt;model variety vs. hardware constraints&lt;/strong&gt;.&lt;br&gt;
What’s your favourite local llm model ?&lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/7746/my-experience-running-ollama-local-models</guid>
<pubDate>Mon, 24 Nov 2025 16:56:10 +0000</pubDate>
</item>
<item>
<title>Build AI Workflows Visually, Fully Local &amp; Private with Agentic Signal</title>
<link>https://coderlegion.com/4875/build-ai-workflows-visually-fully-local-private-with-agentic-signal</link>
<description>&lt;p&gt;&lt;img src=&quot;https://img.youtube.com/vi/62zk8zE6UJI/maxresdefault.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I just launched &lt;strong&gt;Agentic Signal&lt;/strong&gt; — a visual AI workflow builder that integrates tightly with &lt;strong&gt;Ollama&lt;/strong&gt;. Everything runs &lt;strong&gt;locally and privately&lt;/strong&gt;, so there’s no cloud dependency.   &lt;/p&gt;
&lt;h3 id=&quot;key-features&quot;&gt;Key Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Visual drag-and-drop workflow creation  &lt;/li&gt;
&lt;li&gt;Full streaming support with local LLMs (Ollama)  &lt;/li&gt;
&lt;li&gt;Tool calling and external service integration (email, calendar, cloud storage, browser automation)  &lt;/li&gt;
&lt;li&gt;Conversation memory and structured outputs (JSON schema validation)  &lt;/li&gt;
&lt;li&gt;Model management right from the UI  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tech-stack&quot;&gt;Tech Stack&lt;/h3&gt;
&lt;p&gt;React, TypeScript, Vite, Deno, GraphQL Yoga, React Flow, Material-UI (MUI), Playwright, Docusaurus, JSONata, Ajv, Zod, Bun, Chart.js, SCSS, OAuth 2.0, Google APIs  &lt;/p&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website &amp;amp; Docs → &lt;a rel=&quot;nofollow&quot; href=&quot;https://agentic-signal.com&quot;&gt;https://agentic-signal.com&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;GitHub → &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/code-forge-temple/agentic-signal&quot;&gt;https://github.com/code-forge-temple/agentic-signal&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;Demo Video → &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=62zk8zE6UJI&quot;&gt;YouTube&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;Product Hunt → &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.producthunt.com/products/agentic-signal&quot;&gt;https://www.producthunt.com/products/agentic-signal&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Agentic Signal&lt;/strong&gt; is open source under &lt;strong&gt;AGPL v3&lt;/strong&gt; for personal, educational, and non-commercial use, with a commercial license available for businesses or SaaS integrations.  &lt;/p&gt;
&lt;p&gt;Would love feedback from the community — if you try it out, let me know what you think! &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4875/build-ai-workflows-visually-fully-local-private-with-agentic-signal</guid>
<pubDate>Wed, 20 Aug 2025 17:19:22 +0000</pubDate>
</item>
<item>
<title>How To Run Ollama In Android (Without Root)</title>
<link>https://coderlegion.com/4495/how-to-run-ollama-in-android-without-root</link>
<description>&lt;p&gt;&lt;strong&gt;Yes&lt;/strong&gt;, you can run Ollama directly on your Android device without needing root access, thanks to the Termux environment and its package manager. This turns your smartphone into a portable powerhouse for running local large language models (LLMs). &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/71zirg1ary58w6w385qe.png&quot; alt=&quot;Run Ollama In Android&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;read-full-post&quot;&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://h4ck3r.me/how-to-run-ollama-on-android-termux/&quot;&gt;Read Full Post&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h4&gt;
&lt;p&gt;Before you start, you'll need two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A modern &lt;strong&gt;Android device&lt;/strong&gt;. Performance will heavily depend on your phone's RAM and processor. A device with at least 8 GB of RAM is recommended for a smoother experience.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Termux&lt;/strong&gt; application. It's crucial to install it from &lt;strong&gt;F-Droid&lt;/strong&gt;, as the version on the Google Play Store is outdated and no longer maintained.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;installation-and-setup-guide&quot;&gt;Installation and Setup Guide&lt;/h4&gt;
&lt;p&gt;Follow these simple steps to get Ollama up and running.&lt;/p&gt;
&lt;h5 id=&quot;1-install-and-update-termux&quot;&gt;1. Install and Update Termux&lt;/h5&gt;
&lt;p&gt;First, download and install Termux from the F-Droid app store. Once installed, open the app and update its core packages to ensure everything is current.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pkg update &amp;amp;&amp;amp; pkg upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be prompted several times during the update; it's generally safe to answer with the default option, which is often 'Y' or 'N'.&lt;/p&gt;
&lt;h5 id=&quot;2-install-ollama&quot;&gt;2. Install Ollama&lt;/h5&gt;
&lt;p&gt;With Termux up to date, installing Ollama is as simple as running a single command. The Ollama package is now available in the official Termux repositories.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pkg install ollama
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will download and install the Ollama server and command-line interface (CLI) on your device.&lt;/p&gt;
&lt;h5 id=&quot;3-run-the-ollama-server&quot;&gt;3. Run the Ollama Server&lt;/h5&gt;
&lt;p&gt;Ollama operates as a background server that manages the models. You need to start this server before you can run and chat with an LLM. It's best practice to run this in its own dedicated Termux session.&lt;/p&gt;
&lt;p&gt;Open Termux and start the server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ollama serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll see some log output, indicating the server is running. You need to &lt;strong&gt;keep this session open&lt;/strong&gt;. You can use the &lt;code&gt;termux-wake-lock&lt;/code&gt; command before this to prevent your phone from killing the process when it sleeps.&lt;/p&gt;
&lt;h5 id=&quot;4-download-and-run-a-model&quot;&gt;4. Download and Run a Model&lt;/h5&gt;
&lt;p&gt;Now, open a &lt;strong&gt;new Termux session&lt;/strong&gt; by swiping from the left edge of the screen and tapping &quot;NEW SESSION&quot;. In this new terminal, you can interact with the Ollama CLI.&lt;/p&gt;
&lt;p&gt;To download and start chatting with a model (for example, Mistral), use the &lt;code&gt;run&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ollama run mistral
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first time you run this command, it will download the specified model, which might take some time and storage space. Subsequent runs will be much faster. Once downloaded, you can start chatting with the model directly in your terminal!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;important-tips&quot;&gt;Important Tips&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model Size Matters&lt;/strong&gt;: Mobile devices have limited resources. For better performance, start with smaller models like &lt;strong&gt;Phi-3 Mini&lt;/strong&gt;, &lt;strong&gt;TinyLlama&lt;/strong&gt;, or &lt;strong&gt;Gemma:2b&lt;/strong&gt;. Running larger models like Llama 3 8B might be slow or crash if your phone doesn't have enough RAM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage Space&lt;/strong&gt;: LLMs are large files, often several gigabytes. Ensure you have enough free storage on your device before downloading models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep it Running&lt;/strong&gt;: Android's battery optimization can be aggressive. Use the &lt;code&gt;termux-wake-lock&lt;/code&gt; command in the server session to prevent Termux from being shut down.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Setup Guide</category>
<guid isPermaLink="true">https://coderlegion.com/4495/how-to-run-ollama-in-android-without-root</guid>
<pubDate>Thu, 07 Aug 2025 17:25:15 +0000</pubDate>
</item>
<item>
<title>Using offline AI models for free in your Phyton scripts with Ollama</title>
<link>https://coderlegion.com/1348/using-offline-ai-models-for-free-in-your-phyton-scripts-with-ollama</link>
<description>&lt;blockquote&gt;&lt;p&gt;First published on Substack: &lt;a rel=&quot;nofollow&quot; href=&quot;https://andresalvareziglesias.substack.com/p/using-offline-ai-models-for-free&quot;&gt;https://andresalvareziglesias.substack.com/p/using-offline-ai-models-for-free&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Ollama project allows us to donwload and use AI models for offline usage with our computer resources. This allows us to experiment with the AI in our Python projects without any cost, an testing a lot of models to find the ideal choice for our project. It’s awesome.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17355649620336565286&quot; alt=&quot;Using offline AI models for free in your Phyton scripts with Ollama&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;installation-of-ollama&quot;&gt;Installation of Ollama&lt;/h3&gt;
&lt;p&gt;The installation of Ollama in a Linux device (for MacOS and Windows, check &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ollama/ollama&quot;&gt;Ollama Github page&lt;/a&gt;) is very, very easy. Just write this command in a terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://ollama.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a long wait, Ollama will be fully installed and configured.&lt;/p&gt;
&lt;h3 id=&quot;download-a-model&quot;&gt;Download a model&lt;/h3&gt;
&lt;p&gt;Once installed, we can download any model to our computer for offline usage. In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://ollama.com/library&quot;&gt;Ollama library&lt;/a&gt; page we can read the full list of available models.&lt;/p&gt;
&lt;p&gt;For example, to download gemma2 with 2 billions of parameters, the command will be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ollama pull gemma2:2b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model will be downloaded to &lt;code&gt;/usr/share/ollama/.ollama/models local&lt;/code&gt; folder, if you are curious (as I am).&lt;/p&gt;
&lt;h3 id=&quot;use-the-downloaded-model-in-python&quot;&gt;Use the downloaded model in Python&lt;/h3&gt;
&lt;p&gt;Now, we can use the downloaded Gemma model as any other cloud model, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from ollama import Client, ResponseError

try:
    client = Client(
        host='http://localhost:11434',
        headers={}
    )

    response = client.chat(
        model='gemma2:2b',
        messages=[{
            'role': 'user',
            'content': 'Describe why Ollama is useful',
        }]
    )

    print(response['message']['content'])

except ResponseError as e:
    print('Error:', e.error)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program will output our requested answer. Wonderful!&lt;/p&gt;
&lt;h3 id=&quot;a-real-example-check-this-article-about-ollama-with-ollama&quot;&gt;A real example: check this article about Ollama… with Ollama&lt;/h3&gt;
&lt;p&gt;We can program a very simple article checker with Ollama and Python, like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from ollama import Client, ResponseError

try:
    client = Client(
        host='http://localhost:11434',
        headers={}
    )

    prompt  = &quot;I am an spanish writer that is learning how to &quot;
    prompt += &quot;write in english. Please, review if this article &quot;
    prompt += &quot;is well written. Thank you!\n\n&quot;

    with open('article.md') as f:
        prompt += f.read()

    response = client.chat(
        model='gemma2:2b',
        messages=[{
            'role': 'user',
            'content': prompt,
        }]
    )

    print(response['message']['content'])

except ResponseError as e:
    print('Error:', e.error)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When executed, Gemma will give us a detailer analysis of this article, with advices for improvement.&lt;/p&gt;
&lt;p&gt;Awesome! The possibilities are limitless!&lt;/p&gt;
&lt;h3 id=&quot;a-lot-to-learn-a-lot-of-fun&quot;&gt;A lot to learn, a lot of fun&lt;/h3&gt;
&lt;p&gt;Ollama allows us to test different models with our most precious data, without any privacy concern. Ollama allows to to save costs in the initial stages of development of an AI powered application.&lt;/p&gt;
&lt;p&gt;And you? What kind of projects will you develop witn the help of Ollama?&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
&lt;h3 id=&quot;about-the-list&quot;&gt;About the list&lt;/h3&gt;
&lt;p&gt;Among the Python and Docker posts, I will also write about other related topics, like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Software architecture&lt;/li&gt;
&lt;li&gt;Programming environments&lt;/li&gt;
&lt;li&gt;Linux operating system&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you found some interesting technology, programming language or whatever, please, let me know! I’m always open to learning something new!&lt;/p&gt;
&lt;h3 id=&quot;about-the-author&quot;&gt;About the author&lt;/h3&gt;
&lt;p&gt;I’m Andrés, a full-stack software developer based in Palma, on a personal journey to improve my coding skills. I’m also a self-published fantasy writer with four published novels to my name. Feel free to ask me anything!&lt;/p&gt;
</description>
<category>More Libraries</category>
<guid isPermaLink="true">https://coderlegion.com/1348/using-offline-ai-models-for-free-in-your-phyton-scripts-with-ollama</guid>
<pubDate>Wed, 05 Mar 2025 06:45:38 +0000</pubDate>
</item>
</channel>
</rss>