The difference between a Python demonstration and a deployable Foundry application is usually invisible in the happy-path output. Both can answer a prompt. Only the production application has a stable configuration model, identity flow, typed outputs, errors, tests, versioned dependencies and a safe plan for scaling. An AI-103 developer should understand those responsibilities even when Microsoft Foundry manages the underlying model or agent runtime.
Use a modest example: an authenticated support app receives a question, retrieves approved manual passages, calls a model and returns a cited answer. The code path touches environment configuration, identity, a project endpoint, retrieval, response validation, logging and error handling. The goal is not to memorize a particular SDK constructor; those evolve. The goal is to understand the contracts the application must enforce around any SDK.
Choose the current client and version consciously
Microsoft Foundry has evolved from earlier Azure AI Studio and Azure OpenAI patterns, and new Foundry agent APIs are not interchangeable with every classic example. Before copying code, verify the documented SDK or REST API for the selected project, agent type, model, feature and release. Record package versions in the application’s dependency lockfile and in its release manifest.
The current Microsoft AI-103 study guide expects experience developing with Python. That means candidates should be comfortable reading exceptions, working with JSON, making authenticated requests and integrating SDK outputs into application logic. A snippet that works only because it runs inside an administrator’s interactive notebook does not demonstrate a deployable identity or error model.
Isolate configuration from code and secrets
An application needs project endpoint, deployment or agent ID, environment, timeout settings and observability configuration. Keep those in validated configuration rather than scattering literal strings across handlers. Use a secure secret store or supported workload identity instead of checking keys into source control. Make it impossible for a staging build to silently target a production project because a developer forgot to change one hard-coded URL.
For local development, prefer a supported developer-credential chain with only nonproduction roles. In deployment, use a managed identity or an approved service identity with explicit access. Test with the actual deployed principal: the fact that a local administrator can call a model or search index says little about what a hosted application can do. The managed identity model is especially useful for keeping runtime credentials out of source code.
Validate inputs before calling the model
A request handler should authenticate the caller, enforce allowed input type and size, and check whether requested records belong to that user or tenant. A document upload may need scanning, MIME validation and limits on page count. A model endpoint’s maximum context size is not a reason to accept arbitrary untrusted file content in one request.
Distinguish user instructions from retrieved evidence. If the application allows users to ask about internal documentation, the retrieval service must filter by authorized scope. Do not hand the model the entire index and ask it to promise confidentiality. Reject or sanitize tool options a caller should never control, such as internal endpoint destinations or privileged operation names.
Treat structured output as an input to validation
A language model may produce JSON that conforms to a schema, but semantic correctness still requires checking. Suppose the response contains { "invoice_id": "I-410", "amount": 430 }. The JSON may be parseable while the amount comes from the wrong invoice or the wrong currency. Confirm identifiers and values against source records before using them to drive an action.
Make response validation explicit: parse the object, reject missing required fields or unexpected types, check referential integrity, and decide whether low-confidence or conflicting evidence requires human review. If the model returns invalid output, use a bounded repair path and then fail clearly. An infinite “retry until valid” loop can hide a persistent prompt or source-data defect and amplify cost.
Manage timeouts and retries by operation type
A model inference call may return a retryable rate-limit or transient failure; an invalid credential is not repaired by waiting. Apply capped exponential backoff with jitter only to appropriate error classes. Preserve an overall request deadline so a slow retrieval service cannot consume the entire user-facing response budget before the model is called.
Write tools need stronger guarantees. If an agent creates a draft case and the acknowledgement is lost, a naive retry can create another case. Include an idempotency key bound to the request, query the source of truth on ambiguous outcomes, and ensure the tool enforces authorization each time. Python exception handling should reflect business side effects, not merely catch every exception and re-run the whole function.
A Python application often makes several independent calls: retrieve evidence, invoke a model, validate structured output, and submit a business action. Give each step its own timeout and retry classification. A search request that times out before returning results may be retried with a bounded policy; a payment or order-creation request may already have committed when its acknowledgement disappears. Both are HTTP failures, but they are not the same operational event and should never share a naive decorator that repeats everything.
Keep request correlation IDs outside prompt text, and carry them across retrieval and inference for observability. When the model endpoint returns a rate-limit error, honor server-provided retry information where available and enforce a total user-request deadline. Add jitter to retries to avoid synchronizing many workers after an outage. When the deadline expires, the client should return a controlled error or queued-work identifier—not a fabricated answer presented as if the model completed successfully.
Treat JSON parsing as only the first output check. For a claims summary, the output schema may accept an amount as a number while the source document specifies a different amount. Verify currency, range, identity and evidence reference against the originating record before the application writes anything. A typed response proves structural conformance; it does not prove factual or business correctness. Store test fixtures that expose these distinctions in the same repository as the client implementation.
A deployment smoke test should exercise the service under its production identity, then deliberately inject one retrieval failure, one malformed model response and one ambiguous side-effect timeout. Passing the happy path tells you only that the dependencies were available once. Passing those failure cases provides evidence that the application can fail safely and be repaired without losing the transaction boundary.
Keep retrieval and generation independently testable
Build a retrieval function that accepts a caller’s identity and a query and returns typed passages with source IDs and authorization metadata. Build a generation function that consumes only already-authorized evidence and returns a validated response. This separation makes it possible to test search relevance without paying for model calls and test answer generation against known evidence without requiring a live index.
Use local fixtures containing an outdated document, a conflicting paragraph, and a missing answer. Integration tests should still exercise the real service boundaries, but deterministic unit tests can catch common mapping and validation regressions earlier. Hybrid retrieval improves recall when implemented well; application architecture determines whether its results remain attributable and safe.
Connect tool calls through a trusted execution layer
If the application uses a Foundry agent to select tools, define each capability with a typed schema and an authorized service implementation. Keep the model’s proposal separate from execution. A get_order tool may run immediately after authorization, while a change_address tool may require explicit user confirmation. The model should receive a controlled success or failure object, not a raw database trace.
Avoid letting arbitrary tool result text redefine application control flow. Validate returned fields and lengths, escape content where it enters web output, and treat connected documents as untrusted. A model may summarize text that contains “ignore previous instructions,” but the application must not route that text as a developer instruction or allow it to select arbitrary network destinations.
Instrument the application without creating a data leak
Attach a request ID to user-facing and backend operations. Record deployment version, model identifier, retrieval call count, latency, token estimates, tool outcomes, and exception categories. Use traces to reveal the stage responsible for a failure. Avoid logging entire customer messages, signed URLs or access tokens by default; follow retention and access-control policies for sensitive operational evidence.
Observability should show application and Foundry activity together. The Foundry tracing setup can help correlate agent stages with Azure Monitor telemetry. When an answer is wrong, an engineer should be able to identify the source revision, retrieval query and response version rather than reproducing the issue by repeatedly asking the model the same prompt.
Design CI/CD around compatibility and rollback
Test Python dependencies and schemas in CI before deploying infrastructure or prompts. Use a separate environment for integration tests against real Foundry and search services, with narrowly scoped test data. Record model deployment, project configuration, index version, tool schema and application version as one tested release set.
When a release changes the shape of an indexed document, deploy compatible ingestion before requiring the new field. When a tool API changes, update and test the consuming agent definition before switching traffic. A rollback that restores only Python code may not undo a changed index or agent contract. Good CI/CD makes dependencies and their order visible and keeps an auditable record of what was promoted.
Finish with a production failure rehearsal
A useful AI-103 Python lab deliberately triggers three failures: retrieval returns no authorized passages, the Foundry endpoint rate-limits requests, and a downstream read-only tool returns a permission denial. In each case the application should distinguish the cause, avoid fabricated success and preserve a trace ID. Then test a write tool with a lost acknowledgement and verify that an idempotent retry does not duplicate the operation.
Finally, run the same tests under the production-style managed identity rather than a developer account. This is the point where SDK familiarity becomes software engineering. A durable AI app uses Foundry capabilities as services inside a controlled system, with clear ownership of configuration, data, errors, release and user-facing behavior.