Modern AI applications rarely depend on a single model. A production workload may use different models for simple questions, complex reasoning, coding, tool calling, or latency-sensitive requests. Microsoft Foundry Model Router is designed to handle this model selection automatically.
That flexibility becomes especially useful when a selected model is temporarily unavailable or experiences an endpoint problem. Model Router can automatically fail over to another eligible model instead of forcing the application to implement its own retry and routing logic.
But automatic routing introduces an operational question: How do you know what happened to an individual request?
For production systems, simply knowing that a request succeeded is not enough. Developers may need to know which model served the request, whether another model was attempted first, how long the routing decision took, and whether a fallback occurred.
Microsoft Foundry now provides per-request routing metadata in preview for Chat Completions. This makes it possible to inspect routing behavior at the individual request level and use that information for troubleshooting, observability, and performance analysis.
What Is Microsoft Foundry Model Router?
Model Router is an optimization layer that evaluates an incoming request and selects an eligible model based on the configured routing mode and model pool.
Instead of changing the model name in application code, an application sends requests to the Model Router deployment. The router determines which underlying model should handle each request.
A simplified architecture looks like this:
Application
|
v
Model Router
|
+---- Model A
|
+---- Model B
|
+---- Model C
The important difference from traditional load balancing is that routing is based on the characteristics of the request rather than simply distributing requests evenly.
For example:
A simple classification request may be sent to a faster model.
A complex reasoning request may be sent to a more capable model.
A tool-heavy request may be routed to a model that is better suited for that workload.
If the selected model encounters a transient endpoint problem, Model Router can attempt another eligible model.
This means model selection can vary from request to request, even when all requests come through the same application endpoint.
Why Request-Level Routing Observability Matters
Automatic routing is useful, but it can make production behavior harder to understand.
Imagine an application where a user reports that one request took noticeably longer than another. If both requests were sent to the same Model Router deployment, the application logs might initially show only a successful response.
Without routing information, several questions remain unanswered:
Which underlying model handled the request?
Did Model Router initially select another model?
Did a fallback happen?
How many models were attempted?
How long did the routing decision take?
Which attempt ultimately succeeded?
Did the failed attempt return an HTTP error?
These details can be important when investigating latency spikes or intermittent failures.
Per-request routing metadata provides additional visibility into these decisions.
Enabling Per-Request Routing Metadata
The current preview implementation exposes routing information through the Chat Completions response when the preview feature is enabled.
For Python applications, the Azure OpenAI client can request the routing metadata by supplying the appropriate feature header.
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint=endpoint,
api_key=api_key,
api_version="2024-10-21",
default_headers={
"Foundry-Features": "ModelRouterControls=V1Preview"
}
)
The application can then send a normal Chat Completions request through the Model Router deployment.
response = client.chat.completions.create(
model=deployment,
messages=[
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Explain dependency injection in .NET."
}
]
)
The response can contain additional model-selection information when the preview contract returns it.
Because this is a preview capability, production applications should avoid assuming that every response will contain identical metadata.
Understanding the Routing Trace
The most useful part of the metadata is the routing trace.
A routing trace can show the routing decision and the ordered attempts made for that request.
A simplified example looks like this:
{
"model_selection_details": {
"model_router_details": {
"mode": "balanced",
"routing_trace": [
{
"latency_ms": 19,
"attempts": [
{
"model": "model-a",
"result": {
"status": 404
}
},
{
"model": "model-b",
"result": {
"status": 200
}
}
]
}
]
}
}
}
This tells us considerably more than a normal successful response.
In this example:
Model Router used Balanced mode.
The routing decision reported 19 milliseconds of routing latency.
The first model was attempted.
The first attempt returned an HTTP 404.
A second model was attempted.
The second model returned HTTP 200.
The second model ultimately served the request.
This is the type of information that helps distinguish a normal routed request from a request that required fallback.
Detecting an Automatic Fallback
A fallback occurs when the initially selected model cannot successfully handle the request and Model Router moves to another eligible model.
Conceptually, the request flow looks like this:
Request
|
v
Model Router
|
v
Model A
|
| Failure
v
Model B
|
| Success
v
Response
From an application's perspective, the request may still succeed without custom retry code.
However, operationally, the request was different from a normal single-attempt request.
That distinction matters.
A small number of fallbacks may be expected during transient service conditions. A growing fallback rate, however, can indicate an underlying availability or configuration problem.
For example:
Requests: 100,000
Successful responses: 99,950
Requests with fallback: 1,200
The overall success rate may look healthy, but the fallback count tells a different operational story.
Tracking the Serving Model
The response model field identifies the model that ultimately handled the request.
For example:
serving_model = response.model
print(f"Serving model: {serving_model}")
This can be included in application telemetry.
A useful log record might contain:
Request ID
Timestamp
Serving model
Routing mode
Routing latency
Attempt count
Fallback detected
Response latency
Token usage
HTTP status
This creates a much more useful operational record than simply logging whether the API request succeeded.
Measuring Routing Latency
Routing latency is different from the total response latency.
Consider this simplified request:
Client
|
|---- Router decision: 20 ms
|
|---- Model inference: 850 ms
|
v
Response
The routing decision contributes only a small portion of the overall request duration.
This distinction becomes important when analyzing performance.
Suppose an application's p95 latency increases from 1.2 seconds to 2.1 seconds.
The routing trace can help determine whether the additional latency is associated with routing or with model execution and fallback behavior.
For production monitoring, it is useful to track at least:
Join the conversation! Your thoughts help the community grow.