Knowledge base

I Was Building Around AI Before AI Was Ready

OmniTechs founder Sina Esfahani on turning AI models into system components: APIs, memory, latency, cost and the engineering that still sits around them.

My experience with AI did not start when “AI agents” became a popular phrase.

It started earlier, when the models were considerably less capable, context was expensive, vision was unreliable, latency was a real constraint, and getting an AI model to perform a useful task usually required building quite a lot of machinery around it.

And in a way, that was the interesting part.

I was never particularly interested in calling an AI API just to prove that I could call an AI API.

What interested me was a different question:

How do you turn a model into part of an actual system?

A system that receives information, understands enough of it, makes a decision, talks to another service, stores something, alerts someone, or continues a workflow.

That question has followed me through most of my development career.

And it actually started long before modern AI. I still use it when I take on custom software and workflow work at OmniTechs.

Before AI, there were APIs

More than a decade ago, when I built one of my first commercial websites, I was already fascinated by connecting systems together.

The website was based on WordPress and eventually performed well in Google and generated real sales.

But I did not want the website to exist only as a website.

Telegram was becoming important, so I built a Telegram bot around it.

The bot could read products from the WordPress website, expose the catalogue inside Telegram and let users interact with the store without having to navigate the normal website.

Looking back, the implementation was much simpler than the systems I build today.

But the underlying idea was already there:

take data from one system, transform it into the shape another interface needs, and make the integration feel like one product.

Years later, this became a much bigger part of my work.

Learning by being thrown into problems I didn't yet know how to solve

One project that shaped the way I work was a system called Mita.

The goal was unusual.

We wanted to use a smartphone to measure vibrations and determine the natural frequencies of musical instruments.

The phone's accelerometer collected the measurements. The mobile application sent the data to a server. The server processed the signal, analysed the vibration response and returned the information needed by the application.

I was originally supposed to build the backend.

We agreed that someone else would build the mobile application.

I hired a developer with around eight years of experience.

Halfway through the project, he left.

At the time, I was primarily a Python developer.

But the project still had to be delivered.

So I went through an intensive JavaScript learning period, learned React and React Native, and took over the mobile side myself.

It was not the most comfortable way to learn a new stack.

It was, however, extremely effective.

That experience repeated itself later in different forms.

At another company, much of my work revolved around external APIs. Services such as DataForSEO and other third-party data providers returned information in their own formats.

The frontend did not care how those services represented their data.

It needed our representation.

So my job was often to call multiple services, normalise their responses, aggregate information and expose a predictable API for the rest of the application.

Then came infrastructure.

At one point I was handed a Docker configuration of roughly a thousand lines and, effectively, told:

This doesn't work. Fix it.

There was one slight problem.

I did not really know Docker.

So I learned Docker.

I rebuilt parts of the infrastructure, understood what the system was doing and eventually got it running.

Later I learned Angular because I wanted to be able to take ownership of a larger part of the applications instead of being limited to the backend.

Eventually I also assembled a small team and tried to take on larger projects.

That attempt failed rather spectacularly.

But it taught me something that successful projects rarely teach as efficiently:

software development is not only about knowing how to write code.

Architecture, scope, communication, contracts between components, testing, deployment, ownership and the ability to recover when something goes wrong matter just as much.

Those lessons became surprisingly important once I started working seriously with AI. How I now set those boundaries when AI writes code is a separate story: Before the First Line of Code.

When AI entered the architecture

My serious experimentation with modern AI APIs started roughly a few years ago.

The models were impressive, but compared with what we have today, they were also frustrating.

They could understand the general meaning of something while completely missing the detail that actually mattered.

Vision was limited.

Context windows were smaller and relatively expensive.

Structured interaction with external systems was still developing.

Real-time voice workflows were difficult.

And hallucination was not some theoretical edge case. If you wanted a model to participate in a real workflow, you had to assume that it could misunderstand something.

So instead of expecting the model to magically solve everything, I started building structure around it.

That became the pattern.

The model would perform the part it was good at.

Software would control everything around it.

Experiment 1: an intelligent document inbox

One of my early concepts was a document-management system.

I wanted to be able to send it photographs or scans of documents without manually organising every file.

The workflow was roughly:

document → AI interpretation → classification → decision → action

The model would inspect the document and determine what it appeared to be.

Then the surrounding system would decide what should happen.

If the document required an action, the system could create an alert.

If no immediate action was necessary, it could archive the document.

Important information could be extracted and placed into an index so the document could be found later.

Today, this sounds relatively normal.

At the time, making it reliable was not normal at all.

The interesting part was not simply asking:

“What is this document?”

The interesting part was converting the answer into something software could safely use.

That distinction became increasingly important to me:

AI output is not yet a workflow.

It becomes a workflow only when you define what software is allowed to do with that output.

That is still the standard I use when planning AI document processing: classify, verify, reject, and only then act.

Experiment 2: a Telegram bot that translated a relationship

Another experiment came from an extremely practical problem.

My girlfriend at the time spoke Polish but very little English.

I spoke Persian.

Normal translation apps technically worked, but having a conversation through them was slow and unnatural.

I had already been building Telegram bots for years, so I built another one.

Except this time AI sat in the middle.

I could send a Persian voice message.

The system would process it, translate the meaning and produce something she could receive in Polish.

She could respond from the other side.

Instead of constantly copying text between applications, the translation layer became part of the conversation itself.

But translation created another problem.

Individual sentences are often ambiguous.

Humans solve this almost invisibly because we remember what we were discussing five messages ago, yesterday or last week.

A model without that context can make a technically valid translation that is completely wrong for the conversation.

So I started experimenting with memory.

The system extracted useful information from our previous conversations and made enough of that context available to the model that it could understand what an ambiguous sentence was probably referring to.

That improved the experience considerably.

It also created a new problem:

cost.

More context meant more tokens.

More tokens meant more cost.

And large contexts could also make the entire interaction slower.

Today people talk constantly about “AI memory” and “context engineering.”

For me, those ideas originally came from a much less glamorous problem:

I simply wanted two people who did not share a language to communicate more naturally.

We used the system for several months.

Eventually the project disappeared because the need disappeared.

But the architecture stayed in my head.

And I wanted to take it further.

What if language disappeared from a conversation?

The Telegram experiment led to a much more ambitious concept.

What if two people could make a voice or video call, each speaking their own language, while the other person heard the conversation in their language?

Not subtitles.

Not stopping to translate manually.

A communication layer running between them.

That required a much more demanding pipeline.

Audio had to become text.

The system needed to understand the utterance.

The model had to process it with enough conversational context.

The result had to be translated.

Then it had to become voice again.

And all of that had to happen quickly enough that the conversation still felt like a conversation.

That final requirement was the difficult one.

Latency changes everything.

A model can be incredibly intelligent, but if it takes several seconds to participate in a conversation, the user experience collapses.

We often had to use smaller, faster models.

Those models were faster, but the quality was not always good enough.

So parts of the project were effectively waiting for the technology to catch up.

That has happened several times in my career.

Sometimes an idea is wrong.

Sometimes the implementation is wrong.

And sometimes the idea is simply early.

An AI recruitment system

The same architecture eventually evolved into another concept.

Instead of translating conversations between two people, could AI participate in the conversation itself?

We experimented with a recruitment workflow.

Imagine a new job enters the system.

Instead of somebody manually searching through every profile, the system searches the database and identifies candidates whose information appears relevant to the position.

It can then move those candidates into the next stage.

The larger concept went further.

An AI interviewer could conduct an initial conversation with the candidate.

The person's voice would be transcribed, the conversation would go through the model, and the system could continue the interview based on previous answers and information about the job.

Again, the architecture was possible.

But real-time performance was the constraint.

The best models were not necessarily fast enough.

The fastest models were not necessarily good enough.

And if you are talking to a human, those differences become painfully obvious.

So we stopped pushing some parts of the project.

Not because I stopped believing in the interaction model.

Because the infrastructure around the models had reached the point where improvements in the models themselves would produce disproportionate improvements in the product.

Sometimes waiting is also an engineering decision.

The thing I learned from all of these projects

Working with AI APIs taught me almost the opposite of what many AI demos suggest.

The model is rarely the entire product.

Usually it is one component in a much larger system.

Around it you still need:

  • authentication;
  • APIs;
  • databases;
  • state;
  • permissions;
  • queues;
  • WebSockets;
  • file storage;
  • search;
  • indexing;
  • error handling;
  • retries;
  • validation;
  • logging;
  • user interfaces;
  • fallback behaviour;
  • and rules defining exactly what the AI is and is not allowed to do.

A powerful model can make the system dramatically better.

But it does not remove the need for engineering.

In many cases it increases it.

Because now the system contains a component whose output is probabilistic.

Why I usually build the difficult connections first

This history explains a principle I still use in projects today.

Before spending too much time making something look like a finished product, I usually want to prove the difficult technical relationships.

Can we call the API?

Can we obtain the required data?

Can the systems authenticate?

If something needs to be real-time, can we establish the WebSocket connection?

Can the data move from A to B?

Can the model return information in a structure that the rest of the application can actually use?

Can the latency remain acceptable?

Can the cost remain acceptable?

I want those questions answered first.

Once the uncertain technical connections work, the rest of the application becomes much easier to shape into a product.

This is also how I think about AI.

I am less interested in making an interface that looks intelligent than I am in proving that the intelligence can actually participate in the system behind it.

That is also how I introduce myself on Sina Esfahani — about the founder.

AI got better. The engineering problem changed.

Something interesting happened over the last few years.

Many ideas that were difficult to implement because of model limitations gradually became practical.

Models became better at reasoning over larger amounts of information.

Vision improved enormously.

Structured outputs became more dependable.

Tool use became more sophisticated.

Voice became faster.

Smaller models became surprisingly capable.

Context became easier to manage.

The bottleneck started moving.

Earlier, I often had to ask:

“Is the model good enough to do this?”

Increasingly, the question is:

“How should we architect the system so the model can do this safely, quickly and usefully?”

That is a much more interesting problem.

I don't think of AI as a feature

This is probably the biggest change in how I look at software.

I do not see AI as a box that you add to a product because every product apparently needs an AI button.

I see models as another computational capability.

Sometimes you need a database.

Sometimes you need a search engine.

Sometimes you need a deterministic algorithm.

Sometimes you need an API.

And sometimes you have a problem involving language, ambiguity, classification, extraction, perception or reasoning where a model is the right component.

Then the engineering question becomes:

Where should it sit in the system?

What information should it receive?

What tools should it have?

What should remain deterministic?

What happens when it is uncertain?

What gets remembered?

What should never be remembered?

What happens when the model fails?

What happens when the API disappears?

Can we replace the model without rebuilding the entire product?

Those are the questions that interest me now.

The failed experiments were not wasted work

Several systems I worked on never became commercial products.

Some were too early.

Some became too expensive.

Some depended on models that were not fast enough.

Some simply failed.

I also built teams before I really knew how to manage software teams properly.

I accepted projects before fully understanding their operational complexity.

I learned technologies because I suddenly had no other choice.

But that is also where a large part of my current way of working came from.

Docker was once something I did not know how to use.

React Native was something I learned because the developer responsible for the application disappeared halfway through the project.

AI context management became interesting because literal translation was not good enough for a real conversation.

Real-time AI architecture became important because a five-second delay destroys a natural conversation.

API abstraction became normal because the frontend should not have to understand the internal structure of five different providers.

None of those lessons came from a tutorial project.

They came from systems that actually had to work.

And that is still the kind of engineering I enjoy most.

Not adding technology because the technology is fashionable.

Finding something difficult, proving the uncertain part first, connecting the systems that need to communicate, and then turning those connections into a product somebody can actually use.

That is the standard I bring to custom software projects at OmniTechs.