DEV Community

Daniel Ioni
Daniel Ioni

Posted on

Building Zorgax: A Hybrid AI Copilot with OpenAI, Ollama, Runtime Routing, Grounding, and Vercel Observability

Building Zorgax: A Hybrid AI Copilot with OpenAI, Ollama, Runtime Routing, Grounding, and Vercel Observability

Over the last development cycle, we turned Zorgax from a simple AI chat interface into a real product copilot inside MyZubster.

The goal was not simply to connect an LLM to a textarea.

We wanted an assistant that could decide which model should handle a request, control cloud AI costs, fall back safely, expose what model actually generated a response, avoid presenting experimental features as production-ready, and remain usable as a normal part of the MyZubster interface.

The result is now running in production.

The architecture

The current flow looks roughly like this:

User

MyZubster /zorgax

POST /api/zorgax/assistant/chat

Access / research policy

Task classification

AI Router
├── OpenAI
│ └── complex / research tasks

└── Ollama / local gateway
└── standard tasks + fallback

Zorgax system persona

Product grounding

Response metadata

UI

One important principle is that the frontend does not pretend every response comes from the same AI.

The backend returns information such as:

{
"ai_provider": "openai",
"ai_model": "gpt-5.6-sol",
"ai_fallback_reason": null
}

The chat can then display:

AI: OpenAI · gpt-5.6-sol

If the cloud route fails or is unavailable, the user can instead see something like:

AI: Ollama · qwen2.5:3b · fallback: openai_error

That small UI detail makes the system much easier to debug and much more transparent.

Routing instead of sending everything to the cloud

We did not want every "hello" to consume a remote AI request.

Zorgax first classifies the task.

At the moment, signals such as GitHub, deployment, debugging, research, LIFE, KPI/MRV, automation, long prompts, or explicit research mode can move a request into the complex tier.

Conceptually:

const tier =
useResearch ||
complexSignalCount >= 2 ||
text.length >= 2500
? "complex"
: "standard";

Standard tasks can remain on the local/general AI route.

Complex requests can use OpenAI when the remote provider is configured and the budget allows it.

This gives us a hybrid architecture rather than a hard dependency on a single provider.

OpenAI is enabled by runtime state

One bug we discovered while testing production was especially useful.

The OpenAI API key was correctly configured in Vercel, but Zorgax was still answering through Ollama.

Our runtime logs showed:

aiProvider: ollama
aiModel: qwen2.5:3b
aiFallbackReason: astra_disabled

Instead of guessing, we added safe runtime observability.

The status endpoint now exposes configuration state without exposing any secret:

{
"ai": {
"openai_configured": true,
"astra_enabled": true,
"astra_kill_switch": false,
"astra_model": "gpt-5.6-sol"
}
}

The API key itself never leaves the server.

We also simplified the routing rule: if OPENAI_API_KEY exists, OpenAI can be used unless an explicit emergency kill switch is enabled.

OPENAI_API_KEY configured
+
ZORGAX_ASTRA_KILL_SWITCH != true

OpenAI route available

The kill switch remains useful if we ever need to immediately disable remote inference without deleting credentials.

Budget-aware AI routing

Using multiple models is useful only if cost does not become unpredictable.

Zorgax therefore has an application-side monthly OpenAI budget.

Before sending a complex request, the backend checks how much budget has already been consumed.

It also estimates the worst-case request cost using input size and the configured maximum output.

The flow is approximately:

classify request

check monthly spend

estimate request reservation

reserve budget

call OpenAI

record actual token usage

settle reservation

If budget reservation fails, Zorgax can continue through the fallback model rather than simply breaking the chat.

This makes the routing decision operational rather than purely theoretical.

OpenAI Responses API

The cloud path calls the OpenAI Responses API from the server.

The API key stays in the Vercel secret environment and is never sent to the browser.

Conceptually:

const response = await fetch(
"https://api.openai.com/v1/responses",
{
method: "POST",
headers: {
Authorization: Bearer ${process.env.OPENAI_API_KEY},
"Content-Type": "application/json"
},
body: JSON.stringify({
model,
input,
max_output_tokens: 4096
})
}
);

Usage information from the response is then recorded by our budget service.

If OpenAI returns an error, Zorgax releases the reserved budget and moves to the fallback AI path.

So a temporary remote failure does not necessarily take the assistant offline.

Grounding was as important as model quality

Connecting a stronger model immediately exposed another problem.

A model can produce a convincing description of a product feature that does not actually exist yet.

For MyZubster, that distinction is critical because we have production features, MVP components, research pilots, experimental systems, and roadmap ideas living in the same ecosystem.

We therefore added explicit capability states to the Zorgax system instructions:

LIVE / IMPLEMENTED
PILOT / EXPERIMENTAL
PROPOSED / PLANNED
UNKNOWN / UNVERIFIED

The assistant is instructed not to silently convert one state into another.

For example, Zorgax should not claim that:

Seller → full shipping/order dashboard

exists unless runtime or product evidence supports it.

It should not claim:

LIFE Pilot → GPS environmental reporting

just because that would be a plausible use case.

And it should not invent:

Seller → virtual Metaverse showroom

because it sounds like something the platform could eventually support.

Instead, unverified ideas have to be described as hypothetical, experimental, proposed, or unknown.

That turned out to be one of the most important changes in the entire implementation.

Product-first prompting

We also changed how Zorgax explains MyZubster.

Previously an assistant could easily answer a basic question with architecture, blockchain terminology, repository details, validation theory, and roadmap explanations.

Technically interesting, but bad onboarding.

The new system persona follows a PRODUCT FIRST rule.

A newcomer asking:

What is MyZubster?

should first hear what they can actually do.

Marketplace, Seller, Community, Metaverse, LIFE Pilot, GitHub, and Zorgax are presented as destinations before internal architecture.

When someone expresses a specific goal, Zorgax changes from directory mode to guided mode.

Instead of dumping ten steps, the preferred interaction is:

current step

expected result

next step after confirmation

The assistant also avoids saying a navigation, login, payment, publication, or persistent action succeeded unless the runtime or user actually confirms it.

Web research

Zorgax can also enrich a request with external information.

The research service currently supports adapters for sources such as Brave Search, Tavily, and Wikipedia, depending on which providers are configured.

Research results are normalized into a structure such as:

{
"label": "W1",
"provider": "wikipedia",
"title": "...",
"url": "...",
"snippet": "..."
}

Those sources are passed into the AI context and can also be rendered separately by the UI.

External content is treated as evidence, not as trusted instructions.

That matters because any web-enabled AI assistant eventually has to deal with prompt injection and unreliable sources.

Persistent writes require confirmation

Zorgax also contains a separate data-entry workflow.

If a user says something like:

Save this observation...

the assistant does not immediately persist it.

The backend first creates a normalized preview and a digest.

The user receives a confirmation token similar to:

CONFERMA ab12cd34

Only after explicit confirmation can the authenticated write endpoint persist the record.

The intended flow is:

ANSWER

UNDERSTAND

COLLECT MISSING DATA

VALIDATE

CONFIRM

SUBMIT

This separation between AI suggestion and persistent action is a pattern we want to keep across MyZubster.

Fixing the chat UX

There was also a much more visible problem: the chat itself.

The original conversation area was too small compared with the rest of the page.

Long Zorgax responses also caused the scroll position to land at the bottom of the message.

That meant the user received a long answer and immediately had to scroll upward to find its beginning.

We changed the layout so that the conversation is now one of the primary elements of the page.

The message viewport is taller, the guided controls have been moved below the conversation, and a newly generated assistant message is positioned at its beginning, not its end.

The logic is approximately:

function scrollMessageToTop(element) {
const delta =
element.getBoundingClientRect().top -
messages.getBoundingClientRect().top;

messages.scrollTo({
top: messages.scrollTop + delta - 12,
behavior: "smooth"
});
}

We also use:

input.focus({ preventScroll: true });

so focusing the textarea does not immediately destroy the scroll position we just calculated.

It sounds minor, but it changes the experience of reading long AI responses dramatically.

Observability in Vercel

We wanted to know what happened without reproducing every bug locally.

Each successful Zorgax message now produces privacy-safe routing metadata in the server logs.

For example:

event: zorgax_message_sent
plan: pro
webResearch: true
sourceCount: 0
aiProvider: openai
aiModel: gpt-5.6-sol
aiFallbackReason: null

This was exactly how we discovered that production was still routing to Ollama even though the OpenAI integration itself was implemented.

The UI, backend response, runtime status endpoint, and Vercel logs now all provide different layers of observability.

The current production state

After deploying the latest changes, the runtime reports:

OpenAI configured: yes
OpenAI/Astra enabled: yes
kill switch: off
remote model: gpt-5.6-sol

Zorgax therefore now has two distinct AI paths:

STANDARD

Ollama / general AI gateway

COMPLEX / RESEARCH

OpenAI

budget + usage tracking

fallback when required

And importantly, the user can see which route was actually used.

What we learned

The interesting part of building an AI assistant was not the API call.

The difficult part was everything around it:

routing

  • cost control
  • fallbacks
  • UI
  • observability
  • product grounding
  • permissions
  • evidence boundaries

A powerful model does not automatically make a reliable product.

In fact, stronger generation makes grounding and runtime evidence even more important because incorrect claims can become more convincing.

Our direction for Zorgax is therefore not:

connect everything to AI.

It is:

let AI reason, but make the application define what is real, what is allowed, what is verified, and what actually happened.

That separation is becoming a core architectural principle inside MyZubster.

MyZubster is open source and evolving in public.

Zorgax is becoming the conversational layer connecting the different parts of the ecosystem while keeping execution, evidence, model routing, and product state explicit.

Next we want to keep expanding that idea across Marketplace, profiles, research workflows, pilot projects, GitHub contribution flows, and the wider MyZubster ecosystem.

We did not come to conquer. We came to build together.

Per DEV userei come titolo:

Building Zorgax: A Hybrid AI Copilot with OpenAI, Ollama, Runtime Routing, Grounding, and Vercel Observability

e come tag:

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.