AI Engineer World's Fair 2026
Productionizing LLM Gateways: Architecture, Tradeoffs and Hard Lessons — Kanish Manuja, Twilio
Read the talk
Productionizing LLM Gateways: Fallbacks, Latency, and the Cost of Control
Kanish Manuja explains how gateway designs trade availability, latency, guardrails, and cost—and why reliable operation requires deliberate choices about providers, timeouts, overload, and governance.
From a talk by Kanish Manuja
At a glance
Ideas worth remembering
A fallback policy needs more than a second provider: it needs compatible behavior, tested capacity, and an opportunity to switch before streamed output commits the response.
Measure P99 per model and route, and set timeouts at the same workload-aware level. Fix reasoning settings where possible; consider delayed duplicate requests for unusually slow calls.
Treat guardrails as fallible dependencies with explicit failure policies, time budgets, and fallback options. Their placement changes both latency and what can be checked before output reaches the user.
Protect the gateway's own availability through granular API-key separation, bounded queues, load shedding, and traffic prioritization. Provider resilience alone does not protect the middleware serving every request.
Central governance and shared team ownership can coexist with decentralized gateway deployments. Evaluate that separation before concentrating company-wide traffic in one dependency.
A gateway exposes choices during failure
Kanish Manuja, a principal engineer at Twilio, opens with a familiar error: “Something went wrong. Please try again.” Behind that short message sits a complex system trying to serve an application while model providers fail. Understanding the message means understanding the decisions that system can still make when a dependency becomes unavailable.
An LLM gateway is middleware between applications and model providers. It handles routing, authentication, fallback, rate limits, and governance. Those responsibilities bring four competing concerns together: availability, latency, guardrails, and cost. During degradation, a gateway cannot maximize all four. Application owners need to decide which compromises their use case can tolerate, and gateway designers need to expose controls that let callers make those choices.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Use fallback to spend the remaining request budget wisely
With a single model provider, the provider's availability limits the application's availability. The usual response to an unreliable dependency is to retry with exponential backoff and jitter, then trip a circuit breaker after enough failures. Manuja argues that this pattern alone is insufficient for LLMs. Calls are slow and expensive, so repeated attempts quickly consume the latency budget and multiply cost and tail latency. Stopping altogether also wastes an opportunity if another healthy provider can serve the request.
His preferred starting point is per-request fallback: try provider A, then try provider B if A fails. For applications with especially strict latency requirements, another option is to send requests to both providers in parallel. That avoids waiting for the first provider to fail before starting the second, but Manuja describes the cost as doubling. The choice therefore depends on how much the application will pay to avoid sequential waiting.
Circuit-breaking ideas still have a role. Once a primary provider has been failing for some time, remove it from the request path, allow a cooldown, and try restoring it after a few minutes. This raises a state-placement decision: failure counters can live in each serving instance's memory or in shared infrastructure across the fleet. Shared counters help the fleet fail over quickly. With local counters, changing the number of instances changes how failures accumulate, so deployment size affects the configuration and the behavior operators should expect.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Compatibility, streaming, and capacity limit recovery
A fallback is not automatically transparent to the application. Even where providers offer an OpenAI API-compatible format, tool-calling schemas, token limits, and stop reasons can differ. A gateway can supply a normalization layer to support cross-provider fallback, but the actual fallback paths still need testing. A similar request format does not establish that every provider will satisfy the same application expectations.
Streaming creates a stronger boundary. Waiting 30 seconds for a wall of text is unacceptable in some applications, so delivering output incrementally is necessary. But once provider A's output has reached the client, the gateway cannot recall it and transparently replace the response with provider B's output. In the streaming flow Manuja describes, the request is committed to its provider. A failure after that commitment can produce the opening error message even when fallback machinery exists: responsiveness has consumed a recovery option.
The backup provider also needs serious capacity planning. Manuja repeatedly sees teams provision and test the primary carefully while giving the fallback less attention. He recommends even greater throughput capacity or headroom for the fallback, because it is the application's last line of defense. Routing successfully to a backup is of little value if that provider cannot carry the traffic when it matters.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Measure latency against the workload
Availability failures tend to announce themselves through errors and pages. High latency can remain quiet while requests cease to be useful. A gateway makes this harder to see because it may serve several kinds of work: embedding and classification requests taking less than a second, chat requests taking 3 seconds, and reasoning requests taking much longer. Combining these into a gateway-wide latency number obscures the different expectations for each workload.
Manuja recommends tracking P99 per model and per route, and setting timeouts per model class and route. Without a timeout, the gateway can continue treating a request as in progress even when it is effectively no longer being served. He identifies missing timeouts as the leading cause of silent outages in this discussion. The reason for route-specific thresholds is straightforward: a duration that is normal for a reasoning model can already constitute an outage for a chat application.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Constrain reasoning variability and hedge slow requests
Reasoning and router models have caused some of Manuja's most painful latency problems. He reports that the same prompt can take anywhere from 2 seconds to 60 seconds, and that production P99 has jumped to 60 seconds without an apparent explanation. He also notes that setting temperature to zero is unavailable in many cases. These observations describe an operational problem with predictability; they do not establish a single cause for the latency spikes.
There is no complete remedy offered, but fixing the reasoning level per route is a useful starting point. Router models can hide which underlying model runs, making another consequential choice less visible to the caller. Manuja's recommendation is to make the controllable parts of a request as consistent as possible, while recognizing that the underlying system remains nondeterministic.
Another option is to hedge the tail: launch an additional request when the primary has already run long enough to cross a chosen threshold. Manuja suggests a P90-based trigger and says this can reduce the P99 tail. The mechanism gives an unusually slow request a second opportunity to finish. He does not specify a precise threshold calculation or promise a particular reduction.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Choose what happens when a guardrail fails
Guardrails address prompt injection, personally identifiable information, and toxic output, including models swearing at customers. But a guardrail is itself a service that can become unavailable or unreliable. Adding it to a request path therefore introduces a failure-policy decision alongside its protective function.
Failing open means serving the request even when the guardrail is down. Failing closed means blocking it and returning unavailability. The choice trades availability against the protection the missing check would have supplied. Manuja offers a toxicity filter as a case where an application might continue serving, but he gives no universal default for all guardrails. The default should represent the worst outcome the particular use case can tolerate.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Give guardrails budgets, alternatives, and deliberate placement
Guardrails need explicit time budgets and timeouts. Manuja's design goal is for the LLM to remain the step that determines request duration, rather than allowing a slow guardrail to dominate it. Guardrails also deserve fallback mechanisms of their own: a secondary provider, secondary checks, or cached decisions can help maintain service when the primary guardrail provider fails. These are options to consider, rather than a claim that every alternative supplies equivalent protection.
Placement determines when a check runs and how much waiting it adds. A pre-hook checks input before the model proceeds; Manuja calls this probably the safest placement, while noting that it adds serial latency. Running checks in parallel can save latency, but he warns that this does not work well with streaming. For structured outputs, he recommends avoiding streaming and running guardrails concurrently. Post-hooks serve another purpose: monitoring and auditing outputs. These placements support different timing and enforcement needs, so they should be chosen deliberately.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Protect the gateway from shared limits and retry storms
The gateway adds a dependency of its own to the application's request path. One risk comes from shared limits: a noisy tenant can interfere with other use cases. Manuja recommends segregating API keys as granularly as possible by route and use case, so unrelated workloads do not unnecessarily share the same exposure to provider limits.
Another risk is overload during a retry storm. Simply scaling out is not a sufficient response, so load shedding should be a capability that teams exercise in runbooks and game days. Internal web-server queues need explicit bounds rather than accepting an unlimited accumulation of requests. If some traffic matters more than the rest, custom prioritization can preserve service for the most important use cases under load. The operational decision is which work to admit when the gateway cannot serve everything.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Centralize governance without concentrating all traffic
Manuja closes the technical discussion by questioning the company-wide central gateway itself. It can become a single point of failure, so teams should examine why they want one. In his experience, the underlying requirement is often centralized governance. Cost tracking and rate-limit management can be coordinated through plugins or custom code while gateways remain decentralized. That creates a path to common governance without requiring all application traffic to pass through one shared gateway deployment.
He recommends exploring those alternatives before adopting one gateway deployment for an entire company. A single team can still manage the system; organizational ownership does not require every workload to share one deployment, even if that deployment is distributed. The talk presents this as an architectural direction rather than a detailed implementation of shared governance.
The closing request is personal and practical. It is his son's birthday, and he has spent part of it talking about circuit breaking. He asks the audience to put the lessons to use by preventing one incident for their customers.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Read the complete timestamped transcript
- 0:01
[music]
- 0:13
I'm Kanesh Manuja. I'm a principal
- 0:15
engineer at Twilio.
- 0:18
Let's start with a quick show of hands.
- 0:20
Who here has seen the message, something
- 0:23
went wrong. Please try again.
- 0:27
Well, we have a few lucky ones and a few
- 0:30
that have had a good lunch. Um, so
- 0:34
behind that simple message is actually a
- 0:37
system that is very complex
- 0:40
that serves you that message despite the
- 0:42
model providers being down.
- 0:45
And that's what we're going to
- 0:46
productionize today or discuss
- 0:48
productionizing today.
- 0:50
So what is an LM gateway? An LLM gateway
- 0:53
is an entry point or a middleware
- 0:55
between your apps and the model
- 0:57
providers behind them. It does a bunch
- 1:00
of things. Routing, authentication,
- 1:02
fallback, rate limits, all kinds of
- 1:04
governance that you can think of.
- 1:08
And right at the heart of the gateway is
- 1:11
a fight between four things. It's
- 1:13
availability, latency, your guardrails
- 1:17
and costs.
- 1:18
In case of a degradation, you cannot
- 1:21
maximize all four. You need to pick what
- 1:24
you want. So with this talk, if you use
- 1:29
an LLM gateway, I want you I want to
- 1:32
help you to make that trade-off for your
- 1:34
use case. And if you design a gateway, I
- 1:37
want you to design or provide those
- 1:39
levers to your callers and customers u
- 1:43
so that your customers are happy.
- 1:46
Let's start with availability.
- 1:50
If you have a single model provider,
- 1:54
their ceiling is your ceiling. Their
- 1:57
outage is your outage.
- 2:03
So in typical software engineering, the
- 2:06
way you tackle unreliable dependency is
- 2:08
by retrying.
- 2:11
Retrying with exponential backoffs, with
- 2:14
jitters. And when all of that fails, you
- 2:17
have a circuit breaker that trips after
- 2:19
you've seen sufficient failures and you
- 2:21
stop calling the damn thing.
- 2:24
This is not enough for LLMs. LLMs are
- 2:27
very different compared to your fast
- 2:29
cheap APIs that you retry on. Retrying
- 2:33
an LLM API eats into your latency budget
- 2:37
really fast. And also tripping over a
- 2:40
circuit breaker when you have another
- 2:43
perfectly fine model provider to route
- 2:45
to doesn't make sense. You should use
- 2:47
the second model provider. And third, as
- 2:51
I said, the calls are slow and
- 2:53
expensive. So blind retries just
- 2:56
multiply your cost and your tail
- 2:57
latencies.
- 3:00
So what is a better idea here? It is
- 3:03
actually a per request fallback. What
- 3:06
that means is you can actually try model
- 3:08
provider A and then in sequence try
- 3:11
model provider B if your request to
- 3:13
model provider A fails. Another option
- 3:16
to consider here is you can fire
- 3:18
requests to both the providers in
- 3:19
parallel. But that's only if you're
- 3:21
highly highly obsessed with latencies
- 3:25
because that's just going to double your
- 3:26
cost.
- 3:28
Some of the similar circuit breaking
- 3:30
patterns apply here to LMS as well. If
- 3:34
you know that your primary has been
- 3:37
failing for some time, it doesn't make
- 3:39
sense to try it again. You put it, you
- 3:42
take it out of the load balancer or your
- 3:45
request path and put it in a cool down
- 3:48
and then after a few minutes have
- 3:50
passed, try putting that back again.
- 3:53
One interesting choice that you have to
- 3:56
make here is where your failure counts
- 3:58
live.
- 4:00
You can decide to have the failure
- 4:01
counts live in memory on the instances
- 4:04
that are serving your traffic or you can
- 4:07
have shared infra where your failure
- 4:11
counts are shared across the fleet.
- 4:13
There are trade-offs.
- 4:15
If you want quick failovers, then
- 4:18
fleetwide helps. And with instance uh
- 4:22
with local state counters the issue that
- 4:25
you run into is whenever you change your
- 4:26
deployment size your configuration and
- 4:29
your expectations change. So something
- 4:32
to consider.
- 4:35
What that clean diagram did not really
- 4:37
show you are some of the other gotchas
- 4:39
that I'm going to discuss. So fallbacks
- 4:41
are not transparent.
- 4:43
While the industry is converging on an
- 4:45
OpenAI API compatible format, I would
- 4:49
say there are still nuances. So you need
- 4:50
to really test your fallbacks well. They
- 4:53
can have differences in your tool
- 4:55
calling schemas, token limits, stop
- 4:57
reasons and what have you. So with LM
- 4:59
gateways, you can have a normalization
- 5:02
layer that can ensure that you can do
- 5:05
cross provider fallbacks as well.
- 5:08
Another thing is streaming
- 5:14
it.
- 5:16
So essentially nobody wants to wait for
- 5:20
30 seconds to have a wall of text appear
- 5:22
in front of them. So there are use cases
- 5:24
where streaming is absolutely required.
- 5:27
But it comes as at a cost. You trade
- 5:29
away your levers. You cannot once you
- 5:31
have decided to go with provider A, you
- 5:34
have to continue going with provider A.
- 5:37
You cannot mid-stream change the
- 5:39
providers. Whatever has been sent to the
- 5:42
client, it's done. And that's where the
- 5:45
something uh went wrong message, that's
- 5:47
the one that you see. It's not because
- 5:50
of laziness. It's by design uh that you
- 5:52
see that and it's one of the trade-offs.
- 5:55
I would like to call out one other thing
- 5:57
where I've seen teams trip over and over
- 6:00
again. They really provision and test
- 6:03
their primary providers really well, but
- 6:07
they the second provider, the fallback
- 6:09
provider doesn't necessarily get the
- 6:10
same level of love. And I would argue
- 6:13
that your throughputs or your capacity
- 6:15
or your headroom should be even higher
- 6:18
for the second provider or the fallback
- 6:21
provider because that's your last line
- 6:23
of defense. If that goes down, your
- 6:25
application goes down.
- 6:30
Let's discuss latencies.
- 6:32
Availability failures are right in your
- 6:34
face. They fail. You get alarmed. You
- 6:38
get paged. But high latencies can be the
- 6:42
quiet ones. And they need to receive
- 6:45
more love um than I would say tuning
- 6:47
your services for just availability.
- 6:54
One thing to call out, a gateway may run
- 6:58
mixed workloads
- 7:00
and you can have embedding embedding
- 7:02
requests that takes just less than a
- 7:04
second. You can have classification
- 7:06
requests that take less than a second.
- 7:08
Uh you have chat requests taking 3
- 7:10
seconds and reasoning requests taking a
- 7:13
long time.
- 7:15
Quick show of hands. If you measure
- 7:18
your aggregate latency for your entire
- 7:20
service.
- 7:22
Well, that was a trick question. Sorry.
- 7:24
You shouldn't. It doesn't make sense.
- 7:25
It's a lie. You should be tracking your
- 7:28
P99 per model per route, not a gateway
- 7:32
wide number. Gateway wide number doesn't
- 7:34
make sense, especially if you're running
- 7:36
mixed workloads. And I hope you're not u
- 7:38
for those who raise your hand. Another
- 7:41
thing that can really I cannot emphasize
- 7:44
this enough is for you to set timeouts
- 7:47
on per model class per route.
- 7:50
That's where that's the number one root
- 7:52
cause of your silent outage. If you
- 7:54
don't have a timeout, your gateway
- 7:57
thinks you're hap your request is being
- 7:58
happily served while it is not. And I'll
- 8:02
leave you with this message for for
- 8:03
latencies. Um, specifically a reasoning
- 8:07
models normal is actually a chat models
- 8:10
outage. So you definitely need to track
- 8:12
latency per route.
- 8:17
Okay, this is the most painful or this
- 8:19
the slide that has given me the most
- 8:20
scarse which is reasoning and router
- 8:24
models. So this is where truly the
- 8:28
latency is unpredictable
- 8:30
and reasoning models they do not give
- 8:34
you
- 8:36
they they're highly undeterministic more
- 8:38
deterministic undeterministic than your
- 8:40
normal models. You cannot set the
- 8:42
temperature to zero in many cases and
- 8:44
the same prompt can take somewhere from
- 8:47
2 seconds to 60 seconds and we've seen
- 8:49
that in production where P99 suddenly
- 8:51
popped to 60 seconds for no good reason.
- 8:54
So that's
- 8:56
while there's no magical solution to it.
- 8:59
I would recommend that you at least
- 9:01
start with fixing the reasoning level
- 9:03
per route. So with router models, they
- 9:07
hide that abstraction behind you. Like
- 9:09
they pick which models to run and I
- 9:12
would highly recommend that you at least
- 9:15
make as much uh you make requests as
- 9:18
determinist deterministic as possible
- 9:20
with an undeterministic system.
- 9:24
Another idea is hedging the tail. You
- 9:28
can have a you can fire another request
- 9:30
if your primary request actually
- 9:32
consumed let's say P90 of your latency
- 9:35
budget.
- 9:37
This can hedge the t this can really
- 9:39
hedge the P99 tail u for for your
- 9:43
services.
- 9:45
All right. This is one of my favorite
- 9:47
ones. Um
- 9:49
to keep your model secure you need to
- 9:52
have guardrails.
- 9:54
And with that, guardrails are necessary
- 9:57
for preventing your services from prompt
- 9:59
injection attacks, keeping PII filters
- 10:03
in place, having toxicity filters,
- 10:05
keeping the LMS to stop swearing at your
- 10:08
customers, all those good things. But
- 10:12
just like a model provider, there are
- 10:14
trade-offs, too. Guardrails are just
- 10:17
like another service that can go down
- 10:20
that can be unreliable and that's where
- 10:23
you need to choose do you fail open or
- 10:26
do you fail close when I say fail open
- 10:29
you can still serve the request even if
- 10:31
your guardrails are down fail close you
- 10:34
block the request and say hey I'm not
- 10:36
available that's the trade-off between
- 10:38
availability and security to certain
- 10:40
extent while there's a no universal
- 10:42
answer it really depends on your use
- 10:45
case you can decide like for example a
- 10:48
toxicity filter if it's not up and
- 10:50
running you can still serve that
- 10:52
request.
- 10:54
So the default choice should be the
- 10:57
worst case that you can live with.
- 11:03
There are a few things that you can
- 11:05
actually do to improve the behavior of
- 11:09
your systems in face of uh you know
- 11:12
guardrails being down and and managing
- 11:14
just unreliability of the guardrails
- 11:16
themselves. So the first is time budget.
- 11:21
Your request should never be bound by
- 11:24
your guardrail timing. It should always
- 11:27
be the LM that is the rate determining
- 11:29
step. So make sure that you have
- 11:32
timeouts in place and those guardrails
- 11:35
run with a specific time budget.
- 11:38
Another important thing is fallback.
- 11:41
You've heard, you probably know and I've
- 11:43
talked about it. We always discuss
- 11:45
fallbacks with regards to model
- 11:47
providers, but guardrails are critical
- 11:50
services too where you can consider
- 11:53
fallbacks, have secondary provider,
- 11:55
secondary checks, cache decisions uh to
- 11:58
keep your service available when a
- 12:01
guardrail provider is down.
- 12:04
Another interesting choice that pops up
- 12:06
with regards to guardrails is the
- 12:08
placement of the guardrails.
- 12:11
Typically, you can place the guardrail
- 12:14
in three ways. You can have a pre- hook
- 12:17
that runs where the guardrail actually
- 12:19
runs on the input. You can and that's
- 12:22
probably the safest uh but it does add
- 12:24
serial latency uh to your requests.
- 12:28
Another one is in parallel. This is one
- 12:30
of my favorites, but just to call out,
- 12:33
streaming wouldn't work well here with
- 12:35
with parallel. So if you're specially
- 12:38
producing structured output, please
- 12:39
don't stream them. Uh try to save your
- 12:41
latencies and run run these guardrails
- 12:44
concurrently for your structured
- 12:45
outputs. Another one is post hooks. The
- 12:48
these are best for um output monitoring,
- 12:52
auditing your outputs and and so forth.
- 12:58
So, so far we've all I've discussed all
- 13:02
the things that can go wrong with
- 13:04
regards to our dependencies.
- 13:07
We haven't discussed that we are
- 13:09
actually adding another dependency in
- 13:11
the request path itself which is the
- 13:13
central or which is the LM gateway
- 13:15
itself. There are a few things where we
- 13:17
have been bitten by u and we've learned
- 13:19
some lessons that I want to share with
- 13:21
you. If you're working on an LLM gateway
- 13:24
or using one, one is shared limits.
- 13:29
Make sure that your API keys are
- 13:31
segregated per route, per use case to
- 13:35
the most granular possible uh to the
- 13:39
most granular thing that you can
- 13:40
imagine. U
- 13:44
having a noisy tenant can be one of the
- 13:47
biggest problems here.
- 13:49
Another thing is load shedding. This is
- 13:52
a feature that you should uh as part of
- 13:54
your runbooks, game days, uh make sure
- 13:56
that the gateway that you're using
- 13:58
supports load shedding because when you
- 14:01
have a retry storm, it becomes really
- 14:03
hard to just scale out. You cannot
- 14:05
simply scale out services that is under
- 14:07
a retry storm and all these web servers
- 14:11
they have an internal queue and they're
- 14:13
configurable. Make sure that they're
- 14:15
bounded and they cannot request they
- 14:18
cannot accept requests that are
- 14:19
unbounded. And if you want to have some
- 14:21
custom logic, you can even have traffic
- 14:24
prioritization here as well to make sure
- 14:26
under load your most important use cases
- 14:29
get served. Well,
- 14:34
last thing that I wanted to discuss is
- 14:37
the whole idea of a central gateway
- 14:38
itself. It is a single point of failure.
- 14:41
So if you're thinking of having a
- 14:43
central gateway for your entire company
- 14:45
for to LLMs, I would recommend rethink
- 14:49
that and see what are the reasons that
- 14:51
you want it. What I've noticed is that
- 14:54
in most scenarios, it's not the central
- 14:56
gateway that they want. They want
- 14:58
centralized governance.
- 15:00
And there is a path forward where you
- 15:02
can actually decentralize the gateway
- 15:05
and still centralize government
- 15:07
governance. So do not try to centralize
- 15:11
your traffic but you can have plugins,
- 15:14
you can have custom code that can
- 15:16
centralize your governance. Uh
- 15:18
governance can be in the form of cost
- 15:20
tracking, rate limit managing management
- 15:23
and there are other solutions possible.
- 15:25
So explore those before you chart on
- 15:28
having one central gateway for your
- 15:30
entire company. It can be managed by a
- 15:33
single team, but I wouldn't recommend
- 15:35
deploying it as a single deployment for
- 15:39
the entire company even though it's
- 15:41
distributed.
- 15:43
With that said, I want to end this talk
- 15:46
on a personal note. So, it is my son's
- 15:49
birthday today and I'm here talking to
- 15:52
strangers about circuit breaking. So the
- 15:56
least you can do for me is please go and
- 15:58
prevent one incident for me and for your
- 16:01
customers. Thank you. If you have any
- 16:04
questions. Yeah.
- 16:21
>> [music]