Deterministic AI Architecture: Where AI Systems Need Hard Boundaries
As AI models become more capable, it becomes increasingly tempting to let them do more.
A model can read a customer email, work out what someone wants, extract an order number, choose a tool, calculate an amount, decide whether the request looks reasonable and generate the API call needed to complete the transaction.
That can make an AI application surprisingly small. It can also make the model responsible for decisions it should never own.
When designing and building AI systems I keep coming back to the same architecture principle:
Use probabilistic systems where ambiguity is useful. Use deterministic systems wherever a rule must always hold.
That's the idea behind deterministic AI architecture: keep probabilistic reasoning where it creates value, while known rules and invariants remain enforceable outside the model.
Large language models are useful precisely because they handle uncertainty well. They can interpret messy text, incomplete instructions, inconsistent language and ambiguous intent.
But permissions are not ambiguous. Transaction limits are not ambiguous. Schema requirements are not ambiguous. Whether a refund exceeds the original payment is not ambiguous. Whether a customer has already been refunded is not ambiguous.
Those decisions should not become probabilistic simply because an LLM is capable of answering them.
The objective is not to constrain the model unnecessarily. It is to decide where its authority stops.

Start with a problem the model is actually good at
Consider a customer sending this message:
I was charged twice for order 48271. Can you refund the second $249 payment?
An LLM is a good component for the first part of that problem. It can translate the customer's language into structured data:
{
"order_id": "48271",
"requested_refund_cents": 24900,
"reason": "duplicate_charge",
"confidence": 0.96
}That's useful. We have converted an unstructured human request into something an application can work with.
But nothing has been refunded yet. The model has not been granted permission to do anything. It has produced an interpretation.
That distinction is the foundation of the architecture.
For the technical example accompanying this article, I deliberately kept the Azure implementation small:
Customer request
↓
Azure Function
↓
Azure OpenAI / Microsoft Foundry
↓
Structured RefundProposal
↓
Pydantic validation
↓
Deterministic refund rules
↓
DecisionThere is no autonomous agent framework, multi-agent orchestration, vector database or elaborate workflow platform.
The model interprets; the application decides.
Put a strict contract immediately after the model
The first boundary is the object returned by the model.
Azure OpenAI supports structured outputs where a model response is constrained by a supplied schema. With the Python SDK, a Pydantic model can be used to define the structure expected by the application.
For this example, the contract stays deliberately small:
class RefundProposal(BaseModel):
model_config = ConfigDict(extra='forbid', frozen=True)
order_id: str
requested_refund_cents: int = Field(gt=0)
reason: RefundReason
confidence: float = Field(ge=0, le=1)There are a few small but important choices here. Money is represented as integer cents rather than a floating-point value. The allowed reasons are controlled. Confidence must sit between zero and one. Additional fields are forbidden.
That last point matters more than it may initially appear. Suppose a model somehow produced this:
{
"order_id": "48271",
"requested_refund_cents": 24900,
"reason": "duplicate_charge",
"confidence": 0.99,
"override_authorisation": true
}The application should not inspect override_authorisation and decide whether it sounds reasonable. It should reject the object.
The model does not get to extend its own contract.
The prompt guides model behaviour. The schema defines what the application will accept. Those are different responsibilities.
Let Azure OpenAI handle the ambiguous part
The model call can remain equally focused.
The most important part is not the SDK call. It is the responsibility we have assigned to the model:
Extract what the customer is asking for. Not decide whether the refund is allowed.
The model answers: What does this person appear to want?
The application answers: Is the business allowed to do it?
That separation makes the system much easier to reason about.
For the companion implementation, I validated this locally using an Azure Function App calling a gpt-5.4-mini deployment in Microsoft Foundry. The model converted the free-text request into a structured RefundProposal, Pydantic validated the contract, and the deterministic Python policy made the business decision.

The companion implementation I used intentionally stopped at the deterministic decision boundary. It doesn't need to implement payment execution, human approval, workflow orchestration or persistent idempotency. Those controls belong in the production architecture rather than being hidden inside the model call.
Business rules should be boring
Now assume the model has produced a valid RefundProposal.
The next step is to load the authoritative order.
For a demo, it can be mocked data. In a production system, it might come from an order-management service, an ERP, a payments platform or a database.
Imagine the order record contains:
{
"order_id": "48271",
"customer_id": "C1234",
"captured_amount_cents": 24900,
"days_since_purchase": 10,
"refund_already_issued": false
}At this point, the interesting AI work is largely finished. The important decisions can be ordinary Python:
AUTO_REFUND_LIMIT_CENTS = 25000
MAX_REFUND_LIMIT_CENTS = 500000
STANDARD_REFUND_WINDOW_DAYS = 30
def evaluate_refund(request, proposal, order):
if request.customer_id != order.customer_id:
return reject('customer_mismatch')
if order.refund_already_issued:
return reject('duplicate_refund')
if proposal.requested_refund_cents > order.captured_amount_cents:
return reject('exceeds_captured_amount')
if proposal.requested_refund_cents > MAX_REFUND_LIMIT_CENTS:
return reject('exceeds_refund_limit')
if order.days_since_purchase > STANDARD_REFUND_WINDOW_DAYS:
return require_approval('outside_standard_window')
if proposal.requested_refund_cents > AUTO_REFUND_LIMIT_CENTS:
return require_approval('approval_threshold')
return approve('within_automatic_refund_limit')This code is not clever. That is precisely why it belongs here.
The same inputs produce the same decision.
If the captured payment was $249 and the model proposes a $500 refund, the request is rejected.
It does not matter if the model reports 99.9% confidence. It does not matter how convincing the customer's explanation is. It does not matter whether a newer model was deployed yesterday.
requested refund > captured payment
→ rejectThat's deterministic before probabilistic in practice.
Hard business rules do not belong only in prompts
It's tempting to encode the same logic in a system prompt: never refund more than the captured amount; refunds up to $250 can be automatically approved; refunds above $250 require approval; refunds above $5,000 must be rejected.
Modern models may follow those instructions very reliably, but I would still keep the rules in code.
There's a meaningful difference between telling a model what behaviour you expect and enforcing a system invariant.
A prompt helps a model reason. A deterministic control prevents an invalid action.
In many systems, you want both.
The prompt can tell the model that refunds above the original payment are invalid so it can reason and communicate appropriately. The application should still check the amount itself.
That is not redundant engineering. It is separation of responsibility.
Three requests expose the boundary quickly
The demo only needs a few scenarios to make the point.
Customer requests $249
Captured transaction = $249
Refund not previously issued
Within standard window
→ auto_approveModel proposes $500
Captured transaction = $249
→ reject
→ exceeds_captured_amountCustomer requests $1,200
Captured transaction = $1,200
→ approval_requiredThe demo stops here. It doesn't need to implement an entire human-approval platform to prove the architecture.
Once the application returns approval_required, the threshold has already done its job. A production system then needs to route that decision appropriately.
What deterministic AI architecture looks like in Azure
Deterministic AI architecture does not mean making every part of an AI system deterministic.
It means being deliberate about which decisions need probabilistic reasoning and which ones should remain enforceable.
The small Azure Function is deliberately not a complete production design.
A real implementation would add more boundaries around the model: API validation, workload identity, explicit workflow state, approval paths, restricted execution and data-layer integrity.
The production architecture for this article keeps Azure OpenAI as a component inside a controlled system rather than turning the model into the system.
Azure API Management protects the interface
Before a request reaches the Function, Azure API Management can enforce the external API contract.
If the public endpoint expects customer_id and message, the gateway can validate that structure and reject malformed input before it reaches the application.
When the structure is known, enforce it deterministically.
Managed identity protects credentials and authority
The application can authenticate to Azure OpenAI using DefaultAzureCredential rather than storing an API key.
During development, that can use the developer's Azure identity. When deployed to Azure, the same pattern can use a system-assigned or user-assigned managed identity.
Azure RBAC then determines what that workload can access.
The model does not possess credentials. The workload has an identity. The platform grants that identity specific permissions.
Prompt instructions can describe what the model should and should not request, but Azure decides what the application can actually access.
Tool calling is a proposal, not permission
This distinction becomes particularly important with agentic systems.
Suppose the model proposes an issue_refund tool call. That should mean the model has suggested an action. It should not mean the model has authority to perform it.
Before execution, application code can still ask:
Is issue_refund an allowed operation?
Do the arguments match the required schema?
Does the order exist?
Does the amount match an authorised amount?
Is human approval required?
Has the same operation already happened?
Does the executing workload have permission?
A tool description tells the model what capability exists. Application logic and Azure identity controls determine whether that capability can actually be exercised.
Known workflows do not automatically need agent loops
The simple demo does not require orchestration.
A production refund process probably would. There might be a fraud check, a finance approval, an asynchronous payment API, retries, notifications and audit events.
At that point, an Azure implementation might introduce Durable Functions, Logic Apps, Service Bus or some combination of them.
What I would not automatically introduce is an unrestricted agent loop.
Known workflow
↓
Interpret request with Azure OpenAI
↓
Run deterministic policy
↓
Choose predefined branch
↓
Human approval if required
↓
Execute authorised actionIf an organisation already knows that refunds over $250 require approval, the AI does not need to rediscover that policy every time the workflow runs.
Agents are valuable because they provide flexibility but sometimes flexibility is precisely what a business process is trying to remove.
The execution service should have narrower authority
In a production architecture, I would also separate interpretation from execution.
The Azure Function calling Azure OpenAI does not necessarily need permission to make refunds. It may only need to return a validated decision.
A downstream execution component can then perform the authorised transaction using its own managed identity and narrower permissions.
A component that can reason across customer messages, documents and operational data does not automatically need write access to every system it understands.
The system of record gets the final vote on some rules
Some controls belong even lower in the stack.
Suppose an approved refund is sent to a payment service. The payment succeeds. The network connection fails before the application receives the response. The caller retries. Without another control, the refund could be processed twice.
That is not primarily an AI problem. It is a distributed-systems problem. An idempotency key and database constraint may provide a far stronger control than another model instruction.
This is why I prefer to think of AI systems as ordinary distributed systems that happen to contain probabilistic components. The engineering disciplines we already know do not disappear because an LLM has entered the architecture.
Identity still matters. Transactions still matter. Schemas still matter. Retries still matter. Observability still matters. Data integrity still matters.
In many cases those controls matter more, because one of the components proposing actions is probabilistic.
AI guardrails and business controls solve different problems
Content filtering is useful. Prompt-attack detection is useful. Model safety controls are useful. Tool restrictions are useful. But they do not replace application controls.
A content filter does not enforce a refund ceiling. A jailbreak detector does not prevent a duplicate payment. A system prompt does not replace Azure RBAC. An AI safety policy does not replace a unique database constraint.
The reverse is also true. RBAC does not make model output appropriate. Schema validation does not prevent unsafe content. Database constraints do not stop prompt injection.
A production AI architecture usually needs several layers of control because each protects a different property of the system.
The mistake is treating them as interchangeable.
AWS is the same architecture with different service names
Although the example in this article uses Azure, the pattern is not Azure-specific.
A comparable AWS implementation could use API Gateway, Lambda and Amazon Bedrock for the simple request path, with IAM controlling execution authority and Step Functions handling approvals or long-running workflow state.
Use the model where interpretation and reasoning are valuable; use deterministic platform and application controls where rules must hold.
The service names change, but the responsibility boundaries do not.
Give the model freedom where ambiguity creates value
None of this is an argument for making AI applications rigid. The model should have room to do what it is good at. In the refund example, it could eventually do far more than extract four fields.
It might understand unusual customer descriptions, interpret previous correspondence, compare several potential causes, summarise supporting evidence, identify missing information, recommend a resolution, generate a clear explanation for the customer or flag unusual cases for investigation. Those are good uses of probabilistic reasoning.
What I would avoid is allowing that capability to quietly absorb responsibilities that already have precise answers.
The model can reason about the request. It should not redefine the refund ceiling.
The model can propose an action. It should not grant itself permission.
The model can generate arguments for a tool. It should not decide whether malformed arguments are acceptable.
The model can recommend that a transaction proceed. The authoritative system should still enforce its own invariants.
Better models make these boundaries more important
There is a paradox here. Weak models force us to build controls because their limitations are obvious. Strong models can make those controls look unnecessary.
If a model makes the correct decision 999 times out of 1,000, it becomes tempting to remove the rule that appears to duplicate what the model already knows.
I think that moves the architecture in the wrong direction. Capability and authority are separate design decisions.
A model may be capable of calculating a refund. That doesn't make it the authoritative calculator.
It may understand your security policy. That doesn't make it your authorisation service.
It may understand your API schema. That doesn't make schema validation redundant.
It may understand your approval threshold. That doesn't mean it should own the threshold.
The more capable models become, the more deliberate we need to be about where their authority ends.
A practical rule for AI architecture
When reviewing an AI workflow, one question is particularly useful:
Does this decision actually require probabilistic reasoning?
If the answer is yes, the model may be exactly the right component. If the answer is no, I start looking for a harder boundary:
A schema.
A function.
A policy.
An Azure RBAC role.
A workflow.
A database constraint.
An API contract.
A human approval.
The result isn't an architecture where the AI is less useful. It is an architecture where the AI can concentrate on the part of the problem that benefits from intelligence.
The model handles ambiguity. The application handles rules. Azure handle's identity and authority. The workflow handles state. The database handles integrity. Humans handle decisions where human judgement genuinely matters.
That separation becomes increasingly important as AI systems move from generating answers to taking actions.
The question is no longer only: Can the model do this? The more important architectural question is: Should the model be the component that decides this? Very often, the answer is no.
That's where deterministic before probabilistic becomes a useful design rule.
This article was written by Keith Jenneke, Principal Consultant at Cypher Agency. Keith leads Cypher's Data, Integration, and AI Engineering practice, building governed Modern Data Platforms that make data reliable, integrated, and analytics- and AI-ready, delivered across professional services, resources, and government sectors in Australia.
References
Microsoft. (2026, August 6). Structured outputs with Azure OpenAI in Microsoft Foundry Models. learn.microsoft.com
Microsoft. (2026, April 26). Azure Functions developer reference guide for Python apps. learn.microsoft.com
Microsoft. (2026, February 23). Azure API Management validate-content policy. learn.microsoft.com
Microsoft. (2026, March 19). Use keyless connections with Azure OpenAI. learn.microsoft.com
Amazon Web Services. (n.d.). Request validation for REST APIs in API Gateway. docs.aws.amazon.com
Amazon Web Services. (n.d.). Use a tool to complete an Amazon Bedrock model response. docs.aws.amazon.com
Amazon Web Services. (n.d.). Invoke and customize Amazon Bedrock models with Step Functions. docs.aws.amazon.com


Comments