DEV Community

Daniel Ioni
Daniel Ioni

Posted on

From Chatbot to Product Copilot: How We Productionized Zorgax in MyZubster

From Chatbot to Product Copilot: How We Productionized Zorgax in MyZubster

Over the last development cycle, we moved Zorgax from “AI chat inside the product” to a much more complete product copilot architecture.

The interesting part was not connecting an LLM.

The interesting part was everything around it:

routing

  • billing
  • fallbacks
  • grounding
  • observability
  • UX
  • product navigation

This post is a walkthrough of what we implemented in production.

  1. Hybrid routing: OpenAI for complex tasks, local AI for the rest

Zorgax now uses a hybrid AI architecture.

The backend classifies requests broadly into:

standard
complex

Signals like:

GitHub
Vercel
deploy
debug
code
research
LIFE
KPI
MRV
automation

can push a request into the complex tier.

Research mode and very long prompts can also trigger the complex route.

Conceptually:

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

The routing logic then looks roughly like:

STANDARD

Ollama / general AI gateway

COMPLEX / RESEARCH

OpenAI

This avoids sending every trivial prompt to the remote model.

  1. OpenAI runtime enablement

One production issue was especially useful.

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

The runtime logs showed:

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

So the problem was not the API integration itself.

It was the runtime gate.

We changed the logic so that OpenAI is considered available when:

OPENAI_API_KEY is configured
AND
ZORGAX_ASTRA_KILL_SWITCH != true

This means the API key is the primary source of truth, while we keep a dedicated emergency kill switch.

The production status endpoint now exposes safe state:

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

No secret is exposed.

  1. The first real OpenAI request failed with HTTP 429

Once routing was correctly enabled, we hit the next production problem:

OpenAI HTTP 429

Initially that only told us “something is wrong”.

That was not enough.

A 429 can mean different things:

temporary rate limit
credit exhausted
project spend limit
organization usage limit

So we upgraded the error handling.

Now Zorgax extracts and logs safe metadata like:

status
error code
error type
request id

without logging the API key or secret payloads.

  1. Precise fallback reasons

Instead of exposing only:

openai_error

we now classify errors more precisely.

For example:

openai_credit_balance_exhausted
openai_rate_limit
openai_rate_limit_exceeded
openai_project_spend_limit_exceeded
openai_organization_usage_limit_exceeded

This immediately improved debugging.

In our real production test, the system returned:

fallback: openai_credit_balance_exhausted

That told us the code path was working correctly and the remaining problem was simply billing.

After adding API credit, the next request succeeded.

  1. Retry + secondary OpenAI model

For transient 429s, we added controlled retry behavior.

The backend now:

  1. calls the primary OpenAI model
  2. retries once if the error is transient
  3. respects Retry-After when reasonable
  4. optionally tries a second OpenAI model
  5. falls back to Ollama if necessary

The current primary model is:

gpt-5.6-sol

and the secondary OpenAI fallback defaults to:

gpt-5.6-luna

This is different from the final Ollama fallback.

So the real chain can be:

gpt-5.6-sol

retry

gpt-5.6-luna

Ollama

  1. Budget-aware OpenAI usage

We also kept the application-side monthly budget guard.

Before a remote request is sent, the system:

checks current monthly spend
estimates request cost
reserves budget
calls OpenAI
records actual usage
settles the reservation

We improved this too.

The budget is now shared across OpenAI fallback models instead of being split by model.

We also record the actual OpenAI model used.

That matters because if a request starts with gpt-5.6-sol but completes with gpt-5.6-luna, the usage record should reflect reality.

  1. Model-specific cost estimation

We also made cost estimation model-aware.

Instead of assuming one fixed pricing profile, the router now uses different pricing assumptions for different OpenAI models.

Conceptually:

const MODEL_PRICING_USD_PER_M = {
"gpt-5.6-sol": {
input: 4,
output: 20
},
"gpt-5.6-terra": {
input: 2,
output: 12
},
"gpt-5.6-luna": {
input: 0.2,
output: 1.2
}
};

The goal is not just cost accounting.

It is better routing discipline.

  1. Reducing TPM pressure

We also reduced the default maximum OpenAI output allowance from:

4096 tokens

to:

2048 tokens

while keeping it configurable.

That helps reduce:

token pressure
latency
cost
rate-limit risk

for the kind of product guidance Zorgax usually generates.

  1. Showing the actual provider in the UI

One of the most useful changes was extremely simple.

The backend already returns:

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

The frontend now displays it directly below the answer:

AI: OpenAI · gpt-5.6-sol

or:

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

This removes ambiguity.

The user does not have to trust that the intended model was used.

They can see it.

  1. Vercel observability

The server also logs routing metadata for each successful Zorgax request.

For example:

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

This is what let us verify the production request end-to-end.

Not:

“I think OpenAI handled it.”

But:

“This exact production request used OpenAI and had no fallback.”

That is a much better operational standard.

  1. Grounding: the next major problem

Once OpenAI was working correctly, we immediately found another issue.

The model became too conservative.

It answered that almost everything in MyZubster was unverified.

Why?

Because the OpenAI path and the Ollama path were receiving different runtime context.

The local/general AI route had explicit product facts.

The OpenAI route did not.

That created an asymmetry:

OLLAMA
system prompt

  • runtime product facts
  • user prompt

OPENAI
system prompt

  • user prompt

So we fixed the architecture instead of weakening the grounding rules.

  1. One shared canonical runtime context

Now both AI paths receive the same canonical product facts.

We introduced a shared runtime context roughly like this:

RUNTIME PRODUCT FACTS

  • myzubster.com is live
  • /marketplace is a live route
  • Seller starts from Marketplace
  • /social-login is the auth route
  • /metaverse is a live route
  • /life-pilot is a live/pilot route
  • MYZ is internal accounting/reward
  • advanced roadmap features remain unverified unless supported by evidence

This block is injected into both:

OpenAI prompts
Ollama/general AI prompts

and is also used when estimating OpenAI input cost.

That guarantees prompt consistency.

  1. LIVE route does not mean every feature is verified

This distinction became central.

A page can be live without every possible feature being live.

For example:

/metaverse exists

does not prove:

virtual Seller showrooms exist

Likewise:

/life-pilot exists

does not automatically prove:

photo + GPS environmental reporting
scientific validation
public map publishing

So Zorgax now explicitly reasons with categories like:

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

The goal is to avoid both extremes:

hallucinating features

and:

denying real implemented routes

  1. A production test after the grounding fix

After sharing the canonical runtime context, we tested Zorgax with:

Analyze the current state of MyZubster. Tell me which functions are LIVE/IMPLEMENTED, which are PILOT/EXPERIMENTAL, which are PROPOSED/PLANNED, and which are NOT VERIFIED. Do not invent anything.

This time the answer correctly identified:

LIVE

  • Marketplace
  • Seller flow
  • /social-login
  • /metaverse
  • /life-pilot route
  • open-source contribution flow
  • MYZ internal accounting

PILOT

  • LIFE Pilot activities

UNVERIFIED

  • advanced Seller dashboards
  • advanced Metaverse capabilities
  • user-facing IPFS/IPNS flows
  • IoT / robotics integrations
  • external settlement layers
  • LIFE photo+GPS reporting

And the UI showed:

AI: OpenAI · gpt-5.6-sol

The runtime confirmed:

aiProvider: openai
aiModel: gpt-5.6-sol
aiFallbackReason: null

That was the first complete end-to-end success.

  1. Fixing the chat UX

We also spent time on something less glamorous but very important: the chat itself.

Originally:

the conversation area was too small
long answers opened at the bottom
users had to scroll upward to start reading

We changed the behavior so that:

the chat viewport is larger
responses begin from the top
new assistant messages scroll to their beginning
the input focus does not override scroll position

The important UX logic is roughly:

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

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

and:

input.focus({ preventScroll: true });

This made long AI responses much easier to read.

  1. Guided navigation

Zorgax is not meant to be an isolated AI page.

It is part of MyZubster.

So we also improved the guided navigation.

The page now exposes quick paths such as:

Seller
Marketplace
Metaverse
LIFE Pilot
University / research
Community
Party planning

We also fixed spacing and wrapping in the Vai direttamente section so CTA labels do not visually collide.

  1. Home and Marketplace navigation

One final UX problem remained.

Once a user entered Zorgax, it was not obvious how to return to the main product.

We added persistent navigation directly in the Zorgax header:

🏠 Home MyZubster
🛒 Marketplace

These remain visible while using Zorgax.

On mobile they move to their own row.

We also track the Home navigation as part of the existing Zorgax funnel analytics.

This sounds small, but it changes the page from:

standalone AI destination

into:

a real part of the MyZubster product navigation

  1. The current architecture

At this point the flow looks like this:

USER

Zorgax UI

Access + policy

Web research

Task classification

Shared canonical runtime context

AI router
├── OpenAI
│ ├── gpt-5.6-sol
│ ├── retry
│ └── gpt-5.6-luna

└── Ollama fallback

Grounded answer

Provider / model metadata

Vercel observability

User

  1. What the project taught us

The LLM call was still the easiest part.

The real AI product is:

model

  • router
  • cost guard
  • retries
  • fallbacks
  • product truth
  • permissions
  • logs
  • UX
  • navigation

The more capable the model becomes, the more important it is for the application to define:

what is real
what is live
what is experimental
what is allowed
what is verified
what actually happened

That is the direction we are taking with Zorgax.

Not:

let AI control the product.

But:

let AI reason inside a product whose runtime state, permissions, evidence and execution remain explicit.

Current production status

Today, Zorgax in MyZubster has:

OpenAI routing ✅
Ollama fallback ✅
gpt-5.6-sol production path ✅
secondary OpenAI fallback ✅
budget guard ✅
429 diagnostics ✅
billing working ✅
provider/model visibility ✅
shared grounding context ✅
LIVE / PILOT / UNKNOWN separation ✅
larger chat UX ✅
better scrolling ✅
guided navigation ✅
Home navigation ✅
Marketplace navigation ✅
Vercel observability ✅

And this is now running on the live MyZubster deployment.

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

Per DEV userei questo titolo:

From Chatbot to Product Copilot: How We Productionized Zorgax with OpenAI, Ollama, Grounding and Runtime Routing

Top comments (0)