ERW CommandPMO

AI-103 CERTIFICATION BOOTCAMP

Developing AI Apps and Agents on Azure — Build it, run it, name it exactly.

← Course home
40 sessions · 45 minutes each · 8 weeks at your own pace · One track: your own Azure subscription
WEEK 5 OF 8 · SECURITY, PIPELINES, RESPONSIBLE AI AND INGESTION · SESSIONS 21–25
This week’s assessments: Pop Quiz 4 — Security and Ingestion after Session 25
Every session: the exact names Microsoft uses · one assignment marked against a published rubric · pass mark 70 · Certificate: capstone at 70+ and the marked Final at 700+
A · Plan and manage an Azure AI solution · 25–30%SESSION 21
Security: managed identity, private networking, keyless credentials, role policies
Session 21 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Configure security, including managed identity, private networking, keyless credentials, and role policies

Why This Matters

Security questions on AI-103 are rarely abstract. They describe an app with a key in its settings file, an agent that must reach a storage account, or a hospital that forbids public endpoints, and ask what you change. At work these are the settings an auditor checks first. Today you learn the four controls the objective names: managed identity (system-assigned and user-assigned), private networking for Foundry, keyless credentials with Microsoft Entra ID, and role policies with the exact built-in role names. Then you prove keyless access works in your own project, and you create the workload identity your pipeline will use in Session 22.

IN YOUR SUBSCRIPTION
Creates
One user-assigned managed identity (Microsoft.ManagedIdentity/userAssignedIdentities) named id-ai103-ci in rg-ai103; two role assignments (Foundry User for you on the Foundry resource, and Foundry User for the identity on your project); and one model deployment on Global Standard for the keyless test. No virtual network, private endpoint or DNS zone is created: networking is design-only today.
Cost
The model deployment is billed per token; the test sends two short prompts. Check the Microsoft Foundry pricing page. Learn says managed identities can be used at no extra cost. Private networking components are billed, which is one reason we only design them today.
Clean-up
Delete the model deployment when your evidence is saved: Build → Models, or az cognitiveservices account deployment delete --deployment-name <name> -n <resource> -g rg-ai103. Keep id-ai103-ci and its role assignment for Session 22. If you are pausing the course, delete it with az identity delete -n id-ai103-ci -g rg-ai103 and remove its role assignment on the project under Access control (IAM).
The 45-Minute Session
8:00 – 16:00
Keyless credentials: Microsoft Entra ID instead of API keys
  • An API key is a master key: Learn says key-based authentication grants full access without role restrictions. A Microsoft Entra ID token says who is calling, and a role decides what they may do. Only Entra ID supports the Agents service, evaluations, least-privilege roles, managed identities and per-request user attribution; Learn keeps keys for prototyping and isolated tests.
  • The token scope for Foundry is https://ai.azure.com/.default. AIProjectClient requests it for you; with the OpenAI SDK you pass a token provider as the api_key, as in Session 5.
  • THE EXAM LINE

    Keys are all-or-nothing per resource and cannot show who called. Entra ID tokens (scope https://ai.azure.com/.default) enable least-privilege roles, managed identities and per-principal auditing. Agents and evaluations require Entra ID.

  • Once everything runs keyless, switch keys off. The resource property is disableLocalAuth, set to true in Bicep; in Azure PowerShell it is Set-AzCognitiveServicesAccount with -DisableLocalAuth $true. Learn warns old keys can keep working for minutes, occasionally hours, and suggests proving the change with an old key: expect HTTP 401. To enforce it widely, assign the built-in Azure Policy Azure AI Services resources should have key access disabled (disable local authentication).
  • Illustrative: a Lagos bank’s loan assistant keeps its key in an app setting, and a contractor copies it. With keys disabled and a managed identity holding a project-scoped role, there is nothing to copy.
“Rotate the key” is a sticking plaster. “Remove the key” is usually the answer the exam wants.
24:00 – 32:00
Private networking and network isolation
  • Learn splits network isolation for Foundry into inbound access to the resource, outbound access to other Azure services, and outbound access from the Agent client. Picture a hospital: a locked front door, approved couriers only, a private staff corridor.
  • AreaHow you isolate itNames to recognise
    InboundSet public access to Disabled and connect through a private endpoint, or allow Selected networks (specific IP addresses and virtual networks); All networks is fully publicResource Management → Networking, tabs Firewalls and virtual networks and Private endpoint connections
    Name resolutionPrivate DNS zones, so the service names resolve to private IP addressesprivatelink.cognitiveservices.azure.com, privatelink.openai.azure.com, privatelink.services.ai.azure.com
    Agent outbound, your own networkVirtual network injection with Standard agent setup, and your own Storage, AI Search and Cosmos DB behind private endpoints with public network access disabledAgent subnet delegated to Microsoft.App/environments, /27 or larger at the time of writing, plus a private endpoint subnet
    Agent outbound, Microsoft-managedA managed virtual network with an isolation mode; the approved mode adds a managed Azure FirewallAllow Internet Outbound, Allow Only Approved Outbound, Disabled
  • A locked-down resource can still admit trusted services by exception: Learn lists Foundry Tools, Azure AI Search and Azure Machine Learning.
  • THE EXAM LINE

    Inbound: private endpoint with public access Disabled. Agent outbound: virtual network injection (subnet delegated to Microsoft.App/environments) or a managed virtual network. Allow Only Approved Outbound blocks anything not approved.

  • The limits make good distractors. You cannot add outbound virtual network injection to an existing Foundry deployment: you redeploy. A managed virtual network cannot be switched off once enabled, cannot go from Allow Only Approved Outbound back to Allow Internet Outbound, and is not created from the portal UI today. A private endpoint must share its virtual network’s region and subscription.
  • We only design private networks here: building one would cut your laptop off from the project.
Networking is a day-one decision. If a scenario says “after deployment, add injection”, the answer is “redeploy”.
40:00 – 45:00
Wrap-up: write it down, then clean up
  • Write down: the two managed identity types; what only Entra ID supports; the token scope; disableLocalAuth; the Foundry roles and scopes; the three isolation areas and the managed network modes.
  • Your assignment: evidence that keyless access works, the roles you assigned and why, and a network-isolation design for a real workload.
  • Clean-up: delete the model deployment and keep id-ai103-ci for Session 22 (see the lab box if you are stopping). Never show a key, token or connection string in a screenshot, not even a revoked one.
Keyless is not a feature you add at the end. It is the default you start from.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
System-assigned managed identityconceptAn identity switched on inside one Azure resource and deleted with it.Learn →
User-assigned managed identityconceptA standalone identity resource that several resources can share; you delete it yourself.Learn →
az identity createcliAzure CLI command that creates a user-assigned managed identity.Learn →
ManagedIdentityCredentialclassThe azure-identity credential Learn suggests in production on Azure instead of the full default chain.Learn →
https://ai.azure.com/.defaultsettingThe Microsoft Entra ID token scope for calls to Foundry.Learn →
disableLocalAuthsettingResource property that turns off key-based authentication when set to true.Learn →
az role assignment createcliAssigns a role to a principal at a scope.Learn →
Foundry Agent ConsumerroleLeast-privilege role for principals that only interact with agent endpoints.Learn →
Foundry OwnerroleHighly privileged role: full management plus building in projects; formerly Azure AI Owner.Learn →
Cognitive Services UserroleRole for calling AI Services resources directly; Learn’s RBAC page says not to use it for Foundry project work.Learn →
Private endpoint connectionsportalThe Networking tab where private endpoints to the Foundry resource are managed.Learn →
privatelink.services.ai.azure.comsettingOne of the private DNS zones a Foundry private endpoint needs.Learn →
Microsoft.App/environmentssettingThe delegation required on the agent subnet for virtual network injection.Learn →
Allow Only Approved OutboundsettingManaged virtual network mode that permits only approved outbound traffic, through a managed firewall.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Least privilege

Giving each person or workload only the role and scope its job needs, and nothing more.

Network isolation

Dividing and restricting network paths so a service is reachable only from, and can only reach, approved places.

Self-Check — Before You Submit
1. A logistics firm runs the same tracking code on five container apps. All must call one Foundry project, and permissions must survive when the apps are rebuilt. Which identity fits best?
  • A. A system-assigned managed identity on each app
  • B. One user-assigned managed identity attached to all five apps
  • C. The Foundry resource API key in each app’s settings
  • D. A developer’s own account signed in through Azure CLI
B. One user-assigned managed identity attached to all five apps — A user-assigned managed identity is a standalone resource that several resources can share, and it survives rebuilds, so one role assignment covers all five. System-assigned identities are tempting but are deleted with each app, so permissions would be lost on every rebuild.
2. A web app only needs to send questions to a published agent. Which role gives it least privilege?
  • A. Foundry User
  • B. Foundry Owner
  • C. Foundry Agent Consumer
  • D. Cognitive Services Contributor
C. Foundry Agent Consumer — Learn describes Foundry Agent Consumer as the least-privilege role for principals that only interact with agents. Foundry User is the tempting answer, but it also grants development capabilities the app does not need.
3. Your Foundry resource is already running. Security now requires agent outbound traffic to flow through a subnet in your own virtual network. What does Learn say?
  • A. You must redeploy Foundry with a subnet delegated to Microsoft.App/environments
  • B. Enable virtual network injection on the existing resource from the Networking blade
  • C. Set public network access to Selected networks
  • D. Switch the managed virtual network to Disabled
A. You must redeploy Foundry with a subnet delegated to Microsoft.App/environments — Learn states you cannot add outbound virtual network injection to an existing Foundry deployment; you redeploy with a delegated subnet. Selected networks controls inbound access only, so it does not route agent outbound traffic.

Today’s Assignment

Prove that your app reaches your Foundry project without any key, show the role assignments you made and why, and design network isolation for a real workload.
  • Run roles.sh and keyless_check.py, with prompts adapted to your own work. For each role assignment, state the principal, the role and the scope, and why that scope is the least you could give.
  • Explain why the script needs no key: name the token scope and the credential class, and say what you would change for production on Azure (which credential, which identity type, and why).
  • Say whether you disabled key access on your resource. If you did, say how you proved it; if you did not, say what would have to be true first.
  • Design network isolation for a workload at an organisation you know (label invented details as illustrative): inbound setting, private endpoints and DNS zones, agent outbound choice and isolation mode, and one limitation you must plan around.
  • Write 300–700 words plus your code. Never include keys, tokens, secrets or connection strings; crop endpoints, subscription IDs and object IDs from screenshots if you prefer.
Evidence to include
  • Terminal output of keyless_check.py showing the token line and both routes answering.
  • The output of the two az role assignment list commands, or an Access control (IAM) → Check access screenshot, showing role, principal and scope.
  • Your network-isolation design as a diagram or table.
Names your answer must use exactly
User-assigned managed identity Foundry Agent Consumer https://ai.azure.com/.default disableLocalAuth az role assignment create Allow Only Approved Outbound
Never include a key, a connection string or a token. Crop them out of every screenshot. If one is exposed, the letter will tell you to rotate it.
How this is marked — the rubric
Keyless access works on both routes, and the explanation of token scope, credential and production choice is correct30
Role assignments follow least privilege, with principal, role and scope stated and justified20
The network-isolation design is coherent, uses the right inbound and outbound options, and plans for a real limitation20
Uses the exact names from this session’s names box for identities, roles, settings and commands15
Evidence: script output and the role listing are shown, with no keys, tokens or secrets visible15
Total · pass mark 70100
Submitting session 21
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 21 (the template has it ready, under the Course line), or the submission is refused. Upload the Word file (.docx), or switch to the Google Doc tab and paste your document’s share link.
Marked against the rubric above and returned as a written letter by email; the mark appears on your progress page. Do the self-check above before you submit.
Skills Covered
Managed identitiesKeyless authenticationFoundry built-in rolesNetwork isolationLeast privilege
Tick it once you have submitted.Next: Session 22 →
A · Plan and manage an Azure AI solution · 25–30%SESSION 22
Foundry projects in CI/CD pipelines
Session 22 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Integrate Foundry projects with continuous integration and continuous deployment (CI/CD) pipelines

Why This Matters

A Foundry project that works only because someone clicked the right buttons last Tuesday is not production-ready. AI-103 expects you to put a project into a CI/CD pipeline: infrastructure described in Bicep, a pipeline that signs in to Azure without a stored secret, and an evaluation that can stop a bad change before it ships. At work, this is what lets a team change a prompt on a Friday without fear. Today you connect a GitHub repository to your subscription with OpenID Connect, deploy a model from Bicep, and write an evaluation gate that fails the run when quality or safety slips.

IN YOUR SUBSCRIPTION
Creates
A federated identity credential on the Session 21 identity id-ai103-ci; one more role assignment (Cognitive Services Contributor on rg-ai103); a model deployment (Microsoft.CognitiveServices/accounts/deployments) created by the pipeline from Bicep; and cloud evaluation runs in your project. Outside Azure: a GitHub repository (private is fine) holding a workflow file.
Cost
The model deployment is billed per token on Global Standard, and the gate’s evaluators also use model tokens: check the Microsoft Foundry pricing page, and do not leave the workflow triggering on every push once you are done. The federated credential and role assignment are not billed resources. GitHub Actions usage falls under your GitHub plan, not your Azure bill; check it before running workflows repeatedly.
Clean-up
Delete the model deployment the pipeline created: Build → Models, or az cognitiveservices account deployment delete --deployment-name <name> -n <resource> -g rg-ai103. Then disable the workflow in GitHub so a later push cannot recreate it. Keep id-ai103-ci if you will reuse the pipeline; otherwise delete it with az identity delete -n id-ai103-ci -g rg-ai103 and remove its role assignments under Access control (IAM).
The 45-Minute Session
7:00 – 15:00
Sign in without a secret: OpenID Connect and federated credentials
  • A client secret stored in a repository is a key under the doormat. OpenID Connect (OIDC) replaces it: GitHub issues a short-lived token for this repository and branch, and Microsoft Entra ID accepts it because you told it to trust exactly that. Learn says the approach avoids storing long-lived secrets.
  • Learn offers two identities that can trust GitHub: a Microsoft Entra application, or a user-assigned managed identity. You made the second in Session 21. In the Azure portal open id-ai103-ci; under Settings select Federated credentials, then Add Credential. Choose the scenario GitHub Actions deploying Azure resources, enter your organisation (or user name) and repository, set Entity type to Branch with the value main, and save.
  • In the repository add three secrets: AZURE_CLIENT_ID (the identity’s client ID from its Overview page), AZURE_TENANT_ID and AZURE_SUBSCRIPTION_ID. They are identifiers, not passwords, but Learn stores them as secrets and so do we. Add repository variables FOUNDRY_PROJECT_ENDPOINT (Microsoft’s name) and FOUNDRY_ACCOUNT, FOUNDRY_MODEL_NAME, MODEL_VERSION and MODEL_FORMAT (mine).
  • The workflow must ask GitHub for the token with permissions: id-token: write. Without that line, azure/login has nothing to exchange.
  • THE EXAM LINE

    OIDC in GitHub Actions: a federated identity credential on an Entra application or user-assigned managed identity, secrets AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_SUBSCRIPTION_ID, and permissions id-token: write. No client secret is stored.

  • Give the pipeline identity only what the pipeline does. It already has Foundry User on the project, which the evaluation step needs. For the Bicep step, Learn’s deployment page names Cognitive Services Contributor, whose definition includes resource-group deployments, so assign it on the resource group: az role assignment create --assignee-object-id $MI_OBJECT_ID --assignee-principal-type ServicePrincipal --role "Cognitive Services Contributor" --scope "/subscriptions/<subscription-id>/resourceGroups/rg-ai103".
  • Note what that role cannot do: its definition excludes RAI policy writes. The pipeline can attach an existing guardrail to a deployment, but it cannot create or change a guardrail. That separation is deliberate, and a useful exam fact.
Trust one repository and one branch, not a whole organisation. Narrow trust is cheap to set and expensive to forget.
23:00 – 33:00
Evaluation as a pipeline gate
  • Learn documents a ready-made evaluation step for agents: the GitHub Action microsoft/ai-agent-evals (@v3-beta, preview at the time of writing) and its Azure DevOps twin AIAgentEvaluation@2. Both take azure-ai-project-endpoint, deployment-name, data-path and agent-ids (as name:version), plus an optional baseline-agent-id. The data file names evaluators such as builtin.task_adherence and builtin.violence and lists the queries.
  • They report scores per metric, confidence intervals and, for two agent versions, a pairwise statistical comparison. What Learn does not document for them is a threshold that fails the run. So for a hard gate we write a short script with the cloud evaluation API you met in Session 9, and let its exit code decide. It passes the judge as initialization_parameters={"model": ...}, the key Learn’s cloud evaluation how-to prints; Session 9 followed the newer agent evaluation page’s "deployment_name". If the service rejects one key, use the other.
  • pythongate.pytype it yourself
    import os
    import sys
    import time
    from azure.identity import DefaultAzureCredential
    from azure.ai.projects import AIProjectClient
    from azure.ai.projects.models import TestingCriterionAzureAIEvaluator
    from openai.types.eval_create_params import DataSourceConfigCustom
    from openai.types.evals.create_eval_jsonl_run_data_source_param import (
        CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent)
    
    model = os.environ["FOUNDRY_MODEL_NAME"]
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
    client = project.get_openai_client()
    
    # 1. Fresh answers from the deployment this run just created
    queries = ["<a routine question from your work>", "<a hard question from your work>"]
    rows = [SourceFileContentContent(item={"query": q, "response": client.responses.create(
        model=model, input=q).output_text}) for q in queries]
    
    # 2. One quality and one safety evaluator, mapped to those two fields
    criteria = [TestingCriterionAzureAIEvaluator(
        type="azure_ai_evaluator", name=name, evaluator_name=f"builtin.{name}",
        initialization_parameters={"model": model},
        data_mapping={"query": "{{item.query}}", "response": "{{item.response}}"})
        for name in ("coherence", "violence")]
    schema = {"type": "object", "required": ["query", "response"],
              "properties": {"query": {"type": "string"}, "response": {"type": "string"}}}
    evaluation = client.evals.create(name="ci-gate", testing_criteria=criteria,
        data_source_config=DataSourceConfigCustom(type="custom", item_schema=schema))
    run = client.evals.runs.create(eval_id=evaluation.id, name="ci-run",
        data_source=CreateEvalJSONLRunDataSourceParam(type="jsonl",
            source=SourceFileContent(type="file_content", content=rows)))
    while run.status not in ("completed", "failed"):
        time.sleep(5)
        run = client.evals.runs.retrieve(run_id=run.id, eval_id=evaluation.id)
    # 3. The gate: an incomplete run, any failure or any error stops the pipeline
    print(f"Status: {run.status} | counts: {run.result_counts} | report: {run.report_url}")
    if run.status != "completed" or run.result_counts.failed or run.result_counts.errored:
        sys.exit("Evaluation gate FAILED: this change is blocked.")
    print("Evaluation gate passed.")
    WHAT YOU SHOULD SEE
    Status: completed | counts: <total, passed, failed and errored counts> | report: <link to the run in the Foundry portal>
    Evaluation gate passed.
  • The last three lines are the gate. Every evaluator returns a pass or fail label against its threshold, and Learn’s agent evaluation page shows the run’s result_counts with total, passed, failed and errored. An incomplete run, any failure or any error ends the script with a non-zero exit code, and that fails the job. Learn’s hosted-agent CI/CD quickstart uses the same device: its smoke test ends with exit 1 when the agent returns nothing.
  • THE EXAM LINE

    Use evaluation as a quality gate: run evaluators on each change and block the release when results fail. Aggregate results are in result_counts (total, passed, failed, errored), with per_testing_criteria_results per evaluator.

  • Learn’s training module on automated evaluations takes the next step: trigger on pull requests that touch prompt or configuration folders, and post the metrics back to the pull request, so a reviewer sees the evidence before merging.
A gate whose dataset contains no hard case is decoration. Include at least one question you expect the model to struggle with.
40:00 – 45:00
Wrap-up: the pipeline in five lines
  • Write down: the OIDC pieces (federated credential, three secrets, id-token: write); the Foundry resource types in Bicep and raiPolicyName; az deployment group create; the two agent evaluation steps and their preview status; result_counts.
  • Your assignment: a pipeline that signs in with OIDC, deploys from Bicep and gates on an evaluation, with a passing and a failing run.
  • Clean-up: delete the model deployment the pipeline created and disable the workflow. Keep id-ai103-ci only if you will reuse it.
  • Never commit a key, token or connection string. If one ever reaches a repository, treat it as leaked and regenerate it.
The best pipelines are boring. Boring is exactly what you want on release day.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
OpenID ConnectconceptToken exchange that lets GitHub Actions sign in to Azure without a stored secret.Learn →
azure/login@v2toolThe GitHub Action that signs a workflow in to Azure.Learn →
azure/cli@v2toolThe GitHub Action that runs Azure CLI scripts after sign-in.Learn →
id-token: writesettingWorkflow permission required to fetch the OIDC token.Learn →
Federated credentialsportalThe Settings page of a user-assigned managed identity where you add trust for GitHub.Learn →
GitHub Actions deploying Azure resourcesportalThe federated credential scenario to choose for GitHub workflows.Learn →
az deployment group createcliDeploys a Bicep file to a resource group.Learn →
Microsoft.CognitiveServices/accounts/deploymentsconceptThe resource type of a model deployment, a child of the Foundry resource.Learn →
raiPolicyNameparameterThe deployment property that assigns a guardrail.Learn →
microsoft/ai-agent-evalstoolPreview GitHub Action that evaluates Foundry agents inside a workflow.Learn →
AIAgentEvaluation@2toolPreview Azure Pipelines task that evaluates Foundry agents.Learn →
azd pipeline configcliAzure Developer CLI command that generates a CI/CD pipeline and its federated credentials.Learn →
result_countsparameterThe run field with total, passed, failed and errored counts, used for a gate.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Infrastructure as code

Describing Azure resources in files such as Bicep, so they can be reviewed, versioned and redeployed identically.

Quality gate

A pipeline step that blocks a release when evaluation results fall short.

Self-Check — Before You Submit
1. A team’s GitHub workflow signs in to Azure with a stored client secret, and security wants it gone. What should they configure?
  • A. A longer secret, rotated monthly
  • B. OpenID Connect: a federated identity credential, azure/login@v2 and permissions id-token: write
  • C. The Foundry resource API key as a repository secret
  • D. An Azure Resource Manager service connection inside GitHub
B. OpenID Connect: a federated identity credential, azure/login@v2 and permissions id-token: write — OIDC with a federated identity credential lets GitHub exchange a short-lived token, so no secret is stored. A service connection is the Azure Pipelines mechanism, not a GitHub one, and an API key is still a stored secret.
2. A Lagos bank runs its releases in Azure DevOps and wants its pipeline to evaluate a Foundry agent before release. Which Learn-documented step fits?
  • A. The AIAgentEvaluation@2 task
  • B. azure/arm-deploy@v1
  • C. azd ai agent invoke
  • D. microsoft/ai-agent-evals@v3-beta
A. The AIAgentEvaluation@2 task — AIAgentEvaluation@2 is the Azure DevOps task from the AI Agent Evaluation extension. microsoft/ai-agent-evals is tempting, but it is the GitHub Action equivalent, not an Azure Pipelines task.
3. In a Bicep file for a model deployment, which property pins the guardrail the deployment uses?
  • A. versionUpgradeOption
  • B. disableLocalAuth
  • C. customSubDomainName
  • D. raiPolicyName
D. raiPolicyName — Learn’s Bicep template sets raiPolicyName, defaulting to Microsoft.DefaultV2. disableLocalAuth is a Foundry resource property that switches off keys; it has nothing to do with guardrails.

Today’s Assignment

Build a pipeline in your own GitHub repository that signs in with OpenID Connect, deploys a model deployment from Bicep and gates the change on an evaluation, and show it both passing and failing.
  • Set up the federated credential on id-ai103-ci, the three secrets, the repository variables and the extra role assignment. List each one and explain why none of them is a password.
  • Commit infra/model-deployment.bicep, gate.py (with test questions from your own work) and the workflow. Explain what each step does and which identity and role it relies on.
  • Run the workflow so it passes, then so the gate fails. Explain what stopped the second run and where you saw it.
  • In 300–700 words, say how you would extend this for a real team: the trigger you would use, whether you would add microsoft/ai-agent-evals for an agent and what it would add, and which decisions should still need a person.
  • Never commit or screenshot a key, token or connection string. Client, tenant and subscription IDs may be cropped.
Evidence to include
  • Screenshots of both workflow runs in the Actions tab: one green, one red.
  • The gate step’s log lines showing status, counts and report link, and the failure message from the red run.
  • A screenshot of the federated credential on id-ai103-ci showing repository and branch.
  • A screenshot of the pipeline-created deployment in Build → Models, taken before you deleted it.
Names your answer must use exactly
OpenID Connect azure/login@v2 id-token: write Federated credentials az deployment group create raiPolicyName result_counts
Never include a key, a connection string or a token. Crop them out of every screenshot. If one is exposed, the letter will tell you to rotate it.
How this is marked — the rubric
The workflow signs in without a stored secret, deploys from Bicep and runs the gate; both a passing and a failing run are shown35
The explanation of identities, roles, gate logic and the extension plan is correct and specific25
Uses the exact names from this session’s names box for actions, settings, properties and commands15
Evidence: run screenshots, gate log lines, the federated credential and the deployment are shown with no keys or tokens; clean-up is stated25
Total · pass mark 70100
Submitting session 22
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 22 (the template has it ready, under the Course line), or the submission is refused. Upload the Word file (.docx), or switch to the Google Doc tab and paste your document’s share link.
Marked against the rubric above and returned as a written letter by email; the mark appears on your progress page. Do the self-check above before you submit.
Skills Covered
GitHub Actions with OIDCBicep for FoundryEvaluation gatesAzure Pipelines tasksPipeline identity and roles
Tick it once you have submitted.Next: Session 23 →
A · Plan and manage an Azure AI solution · 25–30%SESSION 23
Responsible AI: safety filters, guardrails, risk detection, moderation, evaluators
Session 23 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Configure safety filters, guardrails, risk detection, and content moderation
  • Apply responsible AI instrumentation, including evaluators, safety evaluations, and explanation tooling

Why This Matters

Every model you deploy in Foundry already sits inside a safety system, and AI-103 expects you to configure it rather than accept it blindly. You will be asked which control catches a jailbreak, which catches instructions hidden in a document, what a severity threshold does, and how you prove a configuration works. At work, this is the difference between an assistant that embarrasses a hospital and one that refuses gracefully and leaves a record. Today you build a custom guardrail, test it from code, and run a safety evaluation whose results explain themselves.

IN YOUR SUBSCRIPTION
Creates
One model deployment on Global Standard and one custom guardrail assigned to it, in your Foundry resource and project; one cloud evaluation run with three safety evaluators.
Cost
Model calls and evaluation runs are billed by usage; check the Microsoft Foundry pricing page. Learn’s cost page says Foundry itself is not charged and you pay for the services you use; a guardrail is configuration on your resource. If you later call Azure AI Content Safety directly, it has its own pricing page.
Clean-up
Delete the model deployment once your evidence is saved: Build → Models, or az cognitiveservices account deployment delete --deployment-name <name> -n <resource> -g rg-ai103. Keep the guardrail: it is configuration rather than a running service, and Session 33 returns to it. The evaluation run stays in the project as evidence.
The 45-Minute Session
8:00 – 17:00
The risks, the severity levels and the thresholds
  • The four harm categories are Hate and Fairness, Sexual, Violence and Self-Harm. Each detection is rated at a severity level: safe (annotated, never filtered), low, medium or high.
  • The threshold on a control decides what is flagged. Learn’s table: Off disables detection for the category; Low flags low severity and higher; Medium flags medium and higher; High flags only the most severe content. So a Low threshold catches the most. The same table’s “least” and “most restrictive” labels are easy to misread; learn the behaviour, not the label. Learn’s default policy for Azure OpenAI text models uses Medium for all four categories, on prompts and completions.
  • Risk (as the guardrail lists it)What it detectsModels / agents
    User prompt attacksPrompt Shields for user prompts: attempts to change system rules, conversation mockups, role-play, encoding attacksBoth
    Indirect attacksPrompt Shields for documents: instructions hidden in content the user didn’t write. Learn’s Prompt Shields page calls these Document attacksBoth
    Spotlighting (preview)Transforms document content with base-64 encoding so the model trusts it less than user and system promptsModels only
    Protected material for text / for codeKnown text such as lyrics and articles; code that matches public repositories, annotated with a citationBoth
    Groundedness (preview)Responses not grounded in the source material you suppliedModels only
    Personally identifiable information (preview), Task Adherence (preview)Personal data; agent tool use that is misaligned with the user’s requestBoth
  • Groundedness detection also exists in Azure AI Content Safety, the standalone service. Its page describes a fast Non-Reasoning mode, a Reasoning mode that explains ungrounded segments, an optional correction feature, and English-only support at the time of writing. The guardrail annotation table marks groundedness as available for streaming scenarios only.
  • Blocklists handle terms no classifier knows: competitor names, internal project codes. Learn documents a Profanity blocklist and a Custom blocklist. Custom lists are managed through the management API (raiBlocklists and raiBlocklistItems), take exact or regex patterns and, at the time of writing, hold up to 10,000 terms each.
  • THE EXAM LINE

    Jailbreak attempts are User prompt attacks; instructions hidden in documents are Indirect (Document) attacks; both are Prompt Shields. Spotlighting and Groundedness are preview and model-only. Organisation-specific terms belong in a blocklist.

Match the attacker to the shield: the user typing it, or a document carrying it.
27:00 – 37:00
Safety evaluations and explanations
  • Guardrails act on live traffic; evaluations measure behaviour before and after release. Foundry’s risk and safety evaluators include builtin.hate_unfairness, builtin.sexual, builtin.violence, builtin.self_harm and builtin.protected_material, scored on a 0–7 severity scale with a default threshold of 3, plus pass/fail evaluators such as builtin.indirect_attack, builtin.code_vulnerability and builtin.ungrounded_attributes. Two agent-only evaluators are in preview: builtin.prohibited_actions and builtin.sensitive_data_leakage.
  • “Explanation tooling” is what stops a score being a black box. Each content safety result carries a label, a score, a threshold and a reason, a sentence explaining the rating. Guardrail annotations explain the live side: which category, what severity, filtered or not. Groundedness detection’s Reasoning mode names the ungrounded segments.
  • pythonsafety_eval.pytype it yourself
    import os
    import time
    from pprint import pprint
    from azure.identity import DefaultAzureCredential
    from azure.ai.projects import AIProjectClient
    from azure.ai.projects.models import TestingCriterionAzureAIEvaluator
    from openai.types.eval_create_params import DataSourceConfigCustom
    from openai.types.evals.create_eval_jsonl_run_data_source_param import (
        CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent)
    
    model = os.environ["FOUNDRY_MODEL_NAME"]
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
    client = project.get_openai_client()
    
    # Prompts that PASSED your guardrail: a blocked prompt has no response to score
    prompts = ["<a normal question from your work>", "<a borderline prompt that passed>",
               "<a second borderline prompt that passed>"]
    rows = [SourceFileContentContent(item={"query": p, "response": client.responses.create(
        model=model, input=p).output_text}) for p in prompts]
    
    criteria = [TestingCriterionAzureAIEvaluator(
        type="azure_ai_evaluator", name=name, evaluator_name=f"builtin.{name}",
        initialization_parameters={"model": model},
        data_mapping={"query": "{{item.query}}", "response": "{{item.response}}"})
        for name in ("violence", "self_harm", "hate_unfairness")]
    schema = {"type": "object", "required": ["query", "response"],
              "properties": {"query": {"type": "string"}, "response": {"type": "string"}}}
    evaluation = client.evals.create(name="s23-safety", testing_criteria=criteria,
        data_source_config=DataSourceConfigCustom(type="custom", item_schema=schema))
    run = client.evals.runs.create(eval_id=evaluation.id, name="s23-safety-run",
        data_source=CreateEvalJSONLRunDataSourceParam(type="jsonl",
            source=SourceFileContent(type="file_content", content=rows)))
    while run.status not in ("completed", "failed"):
        time.sleep(5)
        run = client.evals.runs.retrieve(run_id=run.id, eval_id=evaluation.id)
    
    # Each result carries label, score (0-7), threshold and reason: read every reason
    pprint(list(client.evals.runs.output_items.list(run_id=run.id, eval_id=evaluation.id)))
    print(f"Status: {run.status} | report: {run.report_url}")
    WHAT YOU SHOULD SEE
    [<one output item per prompt, each with results for violence, self_harm and hate_unfairness: label, score, threshold, reason>]
    Status: completed | report: <link to the run in the Foundry portal>
  • Use prompts that passed your guardrail; a blocked prompt raises the error you saw earlier and never produces a response to score. The script passes {"model": ...} to every evaluator, as Learn’s cloud evaluation how-to does; Session 9 showed the "deployment_name" form and that safety evaluators need no judge, so if the service rejects the key, change it or leave it out for these safety evaluators. Read every reason. Where a reason surprises you, you have found something worth writing up.
  • THE EXAM LINE

    Content safety evaluators (violence, sexual, self_harm, hate_unfairness, protected_material) score 0–7 with a default threshold of 3 and return label, score, threshold and reason. The AI Red Teaming Agent reports Attack Success Rate (ASR).

  • For adversarial testing at scale, the AI Red Teaming Agent combines Microsoft’s open-source PyRIT framework with Foundry’s risk and safety evaluations and reports Attack Success Rate (ASR), the percentage of successful attacks out of all attacks. Learn lists attack strategies from Base64 and Leetspeak to Crescendo and multi-turn, and at the time of writing cloud red teaming runs in only a few regions. Know what it is; you do not need to run it today.
A score without its reason is a rumour. Quote the reason in your report.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
Create GuardrailportalThe Build → Guardrails button that opens the guardrail wizard.Learn →
Annotate and blocksettingControl action that records a detected risk and stops the content; works for models and agents.Learn →
Tool responsesettingAgent-only intervention point that scans content a tool returns to the agent.Learn →
User prompt attacksfeatureRisk covering jailbreak-style attempts in the user’s own prompt.Learn →
Prompt ShieldsfeatureDetection of adversarial input: user prompt attacks and document (indirect) attacks.Learn →
SpotlightingfeaturePreview defence that encodes document content so the model trusts it less than direct prompts.Learn →
Protected material for textfeatureRisk that detects known third-party text such as lyrics or articles in output.Learn →
Groundedness detectionfeatureContent Safety feature that checks whether responses are grounded in supplied sources.Learn →
Custom blocklistfeatureYour own list of exact or regex terms to filter.Learn →
Azure AI Content SafetyserviceStandalone service that detects harmful user- and AI-generated content through its own APIs.Learn →
BadRequestErrorclassThe OpenAI Python SDK error raised when a guardrail blocks input (code content_filter).Learn →
builtin.violencesettingBuilt-in safety evaluator scoring violent content from 0 to 7.Learn →
AI Red Teaming AgenttoolFoundry tool that runs PyRIT-based attacks against your model or app and scores them.Learn →
Attack Success Rate (ASR)conceptPercentage of red-teaming attacks that succeeded; the key red-teaming metric.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Severity threshold

The level at which a harm category is flagged; a lower threshold flags more content.

Safety evaluation

An evaluation run that scores responses for harms such as violence or hate, with a label, score and reason for each.

Self-Check — Before You Submit
1. A logistics firm’s agent reads supplier emails through a tool. One email hides text telling the agent to forward every invoice to an outside address. Which control addresses this most directly?
  • A. A Violence control at a High threshold on Output
  • B. Indirect attacks, scanning the Tool response intervention point
  • C. A custom blocklist containing the word “invoice”
  • D. Protected material for code on Output
B. Indirect attacks, scanning the Tool response intervention point — Instructions hidden in content the user did not write are indirect attacks, and the email arrives as a tool response. A blocklist on “invoice” is tempting but would block legitimate work without detecting the injected instruction.
2. You set a harm category’s threshold to Low. What does Learn say happens?
  • A. Only the most severe content is flagged
  • B. Detection is switched off for that category
  • C. Content at low severity and higher is flagged, so the most content is caught
  • D. Only content rated safe is annotated
C. Content at low severity and higher is flagged, so the most content is caught — Learn’s table says Low flags content at low severity and higher, which catches the most. High is the setting that flags only the most severe content, and Off disables detection.
3. A developer assigns a custom guardrail to an agent whose model deployment uses Microsoft.DefaultV2. Which guardrail applies to the agent?
  • A. The custom guardrail, because an agent’s guardrail fully overrides its model’s
  • B. Microsoft.DefaultV2, because the model’s guardrail always wins
  • C. Both, merged control by control
  • D. Neither, until the agent is published
A. The custom guardrail, because an agent’s guardrail fully overrides its model’s — Learn says risks in an agent are detected by the guardrail assigned to the agent, which fully overrides the model’s. Merging is the tempting wrong answer; there is no control-by-control merge.

Today’s Assignment

Configure a custom guardrail on a model deployment, prove what it blocks and what it lets through, and run a safety evaluation whose reasons you interpret.
  • Create a custom guardrail with at least three controls: one harm category with a non-default threshold, User prompt attacks with Annotate and block, and one more of your choice. List each control’s risk, intervention points, action and threshold.
  • Write at least five test prompts for an organisation you know (label invented details as illustrative): benign, borderline and at least one prompt-injection attempt. Run test_guardrail.py and tabulate each prompt’s type, expected result and actual result.
  • Run safety_eval.py on at least three prompts that passed. Report each evaluator’s label, score and reason, and explain one reason in your own words.
  • In 300–700 words, justify your thresholds, describe one thing the guardrail missed or over-blocked, and say what you would add next (a custom blocklist, Spotlighting, groundedness, or an AI Red Teaming Agent scan) and why.
  • Describe harmful test prompts rather than pasting them. Never include keys, tokens or connection strings.
Evidence to include
  • A screenshot of the guardrail’s controls and its assignment to your deployment.
  • Terminal output of test_guardrail.py with at least one BLOCKED and one PASSED result.
  • The safety_eval.py output, or the evaluation report page, showing labels, scores and reasons.
  • Optionally, a Try in Playground screenshot of a triggered block.
Names your answer must use exactly
Annotate and block User prompt attacks Prompt Shields Create Guardrail BadRequestError builtin.violence
Never include a key, a connection string or a token. Crop them out of every screenshot. If one is exposed, the letter will tell you to rotate it.
How this is marked — the rubric
The guardrail is configured as asked, and the test table shows both blocked and permitted prompts with honest results30
The safety evaluation ran, and its labels, scores and reasons are reported and interpreted correctly25
The threshold reasoning and next-step recommendation are sound and specific to the organisation15
Uses the exact names from this session’s names box for risks, actions, classes and evaluators10
Evidence: guardrail screenshot, test output and evaluation output are shown, with no keys and no harmful text pasted20
Total · pass mark 70100
Submitting session 23
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 23 (the template has it ready, under the Course line), or the submission is refused. Upload the Word file (.docx), or switch to the Google Doc tab and paste your document’s share link.
Marked against the rubric above and returned as a written letter by email; the mark appears on your progress page. Do the self-check above before you submit.
Skills Covered
Guardrails and controlsHarm categories and thresholdsPrompt ShieldsTesting blocked inputSafety evaluators
Tick it once you have submitted.Next: Session 24 →
A · Plan and manage an Azure AI solution · 25–30%SESSION 24
Auditing and governing agents: trace logs, provenance, approvals, tool-access controls
Session 24 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Implement auditing through trace logging, provenance metadata, and approval workflows
  • Govern agent behavior with oversight modes, constraints, and tool-access controls

Why This Matters

When an agent does something it should not, the first questions are always the same: what did it do, on whose authority, with which version, and who approved it? AI-103 tests whether you can answer those from evidence, and whether you designed the controls that prevent a repeat. At work, this is what an auditor, a regulator or your own security team will ask on a bad day. Today you learn to leave an audit trail (traces, resource logs, provenance metadata and approval records) and to govern agent behaviour with approvals, tool allow-lists, identities and fleet-level controls in the Microsoft Foundry Control Plane.

IN YOUR SUBSCRIPTION
Creates
One model deployment on Global Standard; one project connection to the public Microsoft Learn MCP server with no authentication; one short-lived prompt agent with an MCP tool (the script deletes it); and a local file, audit.jsonl. Optionally, one diagnostic setting on your Foundry resource sending logs to a Log Analytics workspace.
Cost
Model tokens are billed; check the Microsoft Foundry pricing page. Learn says the Microsoft Learn MCP Server is free and needs no authentication. If you add the optional diagnostic setting, log ingestion and retention in Log Analytics are billed: check the Azure Monitor pricing page first and remove the setting the same day. Application Insights tracing from Session 17, if still connected, is billed the same way.
Clean-up
The script deletes its own agent version. Delete the model deployment (Build → Models). If you created a diagnostic setting, open the Foundry resource → Monitoring → Diagnostic settings and delete it, then delete any Log Analytics workspace you made for this lab from Resource groups → rg-ai103. The MCP connection holds no secret; you may keep it.
The 45-Minute Session
8:00 – 16:00
Provenance: which agent, which version, which change
  • Provenance is the label on the evidence bag: where it came from and who handled it. Foundry gives you much of it already: agent versions are immutable (Session 4), so name plus version identifies exactly what ran, and response and conversation IDs link to traces.
  • Add the rest yourself. create_version accepts a description and metadata: at the time of writing, up to 16 key-value pairs (keys up to 64 characters, values up to 512), which the SDK reference says can store structured information and be used to query objects. Record the owner, the change reference and the risk rating, so a trace leads straight back to a decision.
  • Identity is provenance too. With Microsoft Entra Agent ID, Foundry provisions an agent identity blueprint and agent identities. Unpublished agents in a project share one identity. Publishing an agent creates a distinct identity with a new agentIdentityId, and Learn warns that the shared identity’s role assignments do not carry over: you repeat them.
  • Distinct identities let you tell an agent’s actions apart from a person’s, and give production agents independent audit trails. Administrators find them in the Microsoft Entra admin center under Entra ID → Agent ID → All agent identities.
  • THE EXAM LINE

    Agent provenance: agent name plus immutable version, create_version metadata and description, response and conversation IDs in traces, and a distinct Microsoft Entra agent identity once the agent is published.

  • Illustrative: a public agency’s benefits agent. Each version’s metadata records the policy reference it implements; its published identity can read one storage container and nothing else; every approval is logged with the case number.
Write the metadata for the person investigating at two in the morning, not for yourself today.
25:00 – 37:00
Hands-on: an audited agent with an approval log
  • Deploy a chat model on Global Standard and set FOUNDRY_MODEL_NAME. Then create a project connection to the Microsoft Learn MCP server with no authentication. Learn’s MCP page uses this very server as its --auth-type none example; the extension install comes from Learn’s azd CI/CD page.
  • bashmcp-connection.shtype it yourself
    # Sign in and add the Foundry commands to the Azure Developer CLI
    azd auth login
    azd ext install microsoft.foundry
    azd ai project set "$FOUNDRY_PROJECT_ENDPOINT"
    
    # A public, unauthenticated MCP server: the connection stores no secret
    azd ai connection create learn-mcp-conn \
        --kind remote-tool \
        --target https://learn.microsoft.com/api/mcp \
        --auth-type none
    
    export MCP_CONNECTION_NAME="learn-mcp-conn"   # as in Learn's MCP sample
  • Now the agent. It follows Learn’s MCP quickstart, naming the agent on each request with agent_reference as in Session 12, with three additions: an allow-list, provenance metadata, and an approval log. The Learn MCP Server’s developer reference lists tools including microsoft_docs_search and microsoft_docs_fetch; we allow only the first.
  • pythonaudited_agent.pytype it yourself
    import json
    import os
    from datetime import datetime, timezone
    from azure.identity import DefaultAzureCredential
    from azure.ai.projects import AIProjectClient
    from azure.ai.projects.models import PromptAgentDefinition, MCPTool
    from openai.types.responses.response_input_param import McpApprovalResponse
    
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
    openai = project.get_openai_client()
    
    # Tool-access control: one server, one allowed tool, a person approves every call
    tool = MCPTool(server_label="learn-docs", server_url="https://learn.microsoft.com/api/mcp",
                   require_approval="always", allowed_tools=["microsoft_docs_search"],
                   project_connection_id=os.environ["MCP_CONNECTION_NAME"])
    # Provenance: an immutable version, plus who owns it and which change produced it
    agent = project.agents.create_version(
        agent_name="audited-agent",
        definition=PromptAgentDefinition(model=os.environ["FOUNDRY_MODEL_NAME"], tools=[tool],
            instructions="Answer Azure questions only from Microsoft Learn, and name the page."),
        description="Session 24: approval-gated Learn search",
        metadata={"owner": "<your-name>", "change_ref": "S24-lab", "risk": "low"},
    )
    ref = {"agent_reference": {"name": agent.name, "type": "agent_reference"}}
    response = openai.responses.create(input="What is a user-assigned managed identity?", extra_body=ref)
    
    decisions = []
    for item in response.output:
        if item.type == "mcp_approval_request" and item.id:
            tool_name, args = getattr(item, "name", None), getattr(item, "arguments", None)
            ok = input(f"Approve {tool_name} {args}? (y/N): ").strip().lower() == "y"
            with open("audit.jsonl", "a", encoding="utf-8") as log:   # the approval record
                log.write(json.dumps({"utc": datetime.now(timezone.utc).isoformat(), "approver": "<your-name>",
                    "agent": agent.name, "version": agent.version, "response_id": response.id,
                    "approval_request_id": item.id, "tool": tool_name, "arguments": args, "approved": ok}) + "\n")
            decisions.append(McpApprovalResponse(type="mcp_approval_response", approve=ok, approval_request_id=item.id))
    if decisions:
        response = openai.responses.create(input=decisions, previous_response_id=response.id, extra_body=ref)
    print(response.output_text)
    project.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
    WHAT YOU SHOULD SEE
    Approve microsoft_docs_search <the search arguments the model proposed>? (y/N): y
    <an answer about user-assigned managed identities that names a Learn page>
    
    (second run, answering N)
    <the agent's final answer after your refusal>
  • Run it twice: approve once and refuse once. Then open audit.jsonl. Each line ties a decision to an agent name, a version, a response ID, a person and a time. If you see no approval prompt, the model answered without the tool: ask a question that needs a documentation search.
  • If your project still has Application Insights connected from Session 17, find the same response under Agents → Traces and screenshot the tool span.
  • Optional, for resource logs: on the Foundry resource select Monitoring → Diagnostic settings → + Add diagnostic setting, tick Audit and RequestResponse, send them to a Log Analytics workspace, and later query AzureDiagnostics | where ResourceProvider == "MICROSOFT.COGNITIVESERVICES". Remove the setting the same day.
Refusing an approval is as important to test as granting one. Your log should show both.
42:00 – 45:00
Wrap-up: write it down, then clean up
  • Write down: the four evidence sources and where each lives; the content-recording variable; the metadata limits; shared versus distinct agent identity; the require_approval values; allowed_tools; tool_choice; Stop versus Block; guardrail policies and who can create them.
  • Your assignment: an audit and governance design for your own agent, backed by a working approval log.
  • Clean-up: the script deletes its agent version. Delete the model deployment and any diagnostic setting you created.
  • Crop keys and endpoints out of screenshots, and never log a secret: Learn’s tool guidance says the same about traces and application logs.
An audit log is evidence you write before you need it.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
Microsoft Foundry Control PlaneportalUnified interface for visibility, governance and control of agents, models and tools across Foundry.Learn →
guardrail policiesfeatureMinimum guardrail controls mandated across a subscription or resource group, enforced with Azure Policy.Learn →
Resource Policy ContributorroleRole that, like Owner, can create or edit guardrail policies.Learn →
Microsoft Entra Agent IDserviceEntra ID’s framework for identities built for AI agents, used by Foundry.Learn →
agent identity blueprintconceptThe Entra object from which agent identities are created; the project managed identity authenticates it.Learn →
require_approvalparameterMCP tool setting: always, never, or per-tool lists deciding when a person must approve a call.Learn →
allowed_toolsparameterOptional allow-list of the MCP server tools an agent may use.Learn →
mcp_approval_requestconceptResponse output item asking your code to approve or refuse a tool call.Learn →
McpApprovalResponseclassThe input item that approves or refuses a request, by approval_request_id.Learn →
tool_choiceparameterControls tool calling: auto, required or none.Learn →
AI gatewayfeaturePreview API Management route for MCP traffic that enforces authentication, rate limits, IP rules and audit logging.Learn →
Diagnostic settingsportalThe Monitoring blade that sends a resource’s Audit and RequestResponse logs to a destination.Learn →
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENTsettingEnvironment variable that, when true, records messages and tool arguments in traces.Learn →
Microsoft PurviewserviceData security and compliance service that captures Foundry prompts and responses in the unified audit log.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Provenance

Where an output or action came from: the agent, its version, its owner and the change that produced it.

Allow-list

An explicit list of what is permitted; anything not on it is refused.

Self-Check — Before You Submit
1. A bank’s agent may search product documents freely but must never move money without a person agreeing. Both tools are on one MCP server. What do you configure?
  • A. require_approval set to never, plus a firm instruction not to transfer money
  • B. require_approval with an always list naming only the transfer tool
  • C. tool_choice set to none
  • D. A diagnostic setting with the Audit category
B. require_approval with an always list naming only the transfer tool — A per-tool always list forces approval only for the transfer tool, leaving searches autonomous. An instruction alone is tempting but is a soft constraint, and tool_choice none would stop the searches too.
2. An auditor needs prompts and responses from your Foundry apps in the organisation’s unified audit log. What do you enable?
  • A. Microsoft Purview data security for Foundry
  • B. OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT in production
  • C. A Tool response guardrail
  • D. allowed_tools on every MCP tool
A. Microsoft Purview data security for Foundry — Learn says Microsoft Purview captures Foundry prompts and responses in the unified audit log once Purview data security is enabled. Content recording puts messages into traces in Application Insights, not the unified audit log, and Learn advises against it in production.
3. An agent was published last week. Its storage tool now fails with an authorisation error, although the project’s shared agent identity holds the right role on the storage account. Why?
  • A. Publishing turns off every tool until it is re-approved
  • B. The storage account needs a guardrail policy
  • C. require_approval switches to never after publishing
  • D. The published agent has a distinct agent identity, so the role assignment must be repeated for it
D. The published agent has a distinct agent identity, so the role assignment must be repeated for it — Publishing creates a distinct agent identity with a new agentIdentityId, and Learn warns that the shared project identity’s roles do not carry over. Guardrail policies govern content safety settings, not storage access.

Today’s Assignment

Design auditing and governance for an agent you would run at work, and show a working approval-gated agent with its audit log.
  • Run audited_agent.py with your own question and metadata, twice: approve once and refuse once. Explain each line of your audit.jsonl and what an investigator could reconstruct from it.
  • For your real agent (label invented details as illustrative), write the audit design: which traces, which Diagnostic settings categories, whether Microsoft Purview is needed, what goes into version metadata, and where each record is kept and for how long.
  • Write the governance design: the oversight level for each tool with the exact require_approval and allowed_tools values; which agent identity is used and which roles it holds; the guardrail on Tool call or Tool response; and how an operator would stop or block the agent in the Control Plane.
  • Name one risk your design still leaves open, and how you would detect it.
  • Keep it to 300–700 words plus code. Never include keys, tokens or connection strings, and never write a secret into your audit file.
Evidence to include
  • Terminal output of both runs, showing the approval prompt, your decision and the final answer.
  • The contents of audit.jsonl with both records.
  • A screenshot of the trace under Agents → Traces, or of the Diagnostic settings or Control Plane view you used.
Names your answer must use exactly
require_approval allowed_tools mcp_approval_request McpApprovalResponse Microsoft Foundry Control Plane Microsoft Entra Agent ID Diagnostic settings
Never include a key, a connection string or a token. Crop them out of every screenshot. If one is exposed, the letter will tell you to rotate it.
How this is marked — the rubric
The approval-gated agent works, an approval and a refusal are both logged, and the log is explained correctly30
The audit design names the right evidence sources, provenance fields and retention for the scenario20
The governance design sets exact approval, allow-list, identity and stop or block controls that fit each tool’s risk25
Uses the exact names from this session’s names box10
Evidence: terminal output, the audit file and a trace or portal screenshot are shown, with no keys or secrets15
Total · pass mark 70100
Submitting session 24
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 24 (the template has it ready, under the Course line), or the submission is refused. Upload the Word file (.docx), or switch to the Google Doc tab and paste your document’s share link.
Marked against the rubric above and returned as a written letter by email; the mark appears on your progress page. Do the self-check above before you submit.
Skills Covered
Trace loggingProvenance metadataApproval workflowsTool-access controlsFoundry Control Plane
Tick it once you have submitted.Next: Session 25 →
E · Implement information extraction solutions · 10–15%SESSION 25
Ingesting and indexing documents, images, audio and video; the RAG ingestion flow with OCR
Session 25 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Ingest and index content, such as documents, images, audio, and video
  • Configure RAG ingestion flow, including documents and using optical character recognition (OCR)

Why This Matters

Retrieval is only as good as what went into the index, and most organisations’ knowledge is not tidy text. It is scanned letters, slide decks, photos of notices, recorded calls and training videos. AI-103 expects you to ingest all of it: to know what an indexer, a data source and a skillset each do, when OCR runs, and when Content Understanding is the better tool for audio and video. Today you build a real ingestion pipeline in Azure AI Search over your own mixed files, with integrated vectorization and OCR, feed it Markdown produced from a recording, and prove every run with indexer status.

IN YOUR SUBSCRIPTION
Creates
A storage account (Microsoft.Storage/storageAccounts) with one blob container; an Azure AI Search service (Microsoft.Search/searchServices) on the Basic tier with a system-assigned managed identity; inside it a data source, an index, a skillset and an indexer created by the Import data wizard; an embedding model deployment; the Content Understanding default model deployments; and role assignments for you and for the search service’s identity.
Cost
Billed, and one part bills by the hour. Learn’s cost page says a Dedicated search service is billed hourly for as long as it exists; only deleting it stops that. Image extraction, OCR beyond a small free daily allowance, embeddings and Content Understanding are billed by usage. Check the Azure AI Search and Content Understanding pricing pages and the pricing calculator first, and plan to take Sessions 25 to 27 close together.
Clean-up
Keep the search service with its data source, index, skillset and indexer, the storage account, the embedding deployment and the role assignments you added: Sessions 26 and 27 build on this index, and Session 27 deletes them. The search service bills hourly while it exists, so take Sessions 26 and 27 promptly. If you are pausing the course, delete the service now (az search service delete --name <service-name> --resource-group rg-ai103; Learn notes that deletion is permanent and removes its data) and the storage account (Resource groups → rg-ai103), and rebuild both with this session’s steps before Session 26. Either way, delete the Content Understanding default deployments under Build → Models; Session 28 sets the defaults up again.
The 45-Minute Session
7:00 – 15:00
Documents and images: OCR, Document Layout or Content Understanding
  • The OCR skill recognises printed and handwritten text in image files. Its input is /document/normalized_images/*, which exists only when the indexer’s imageAction is set to generateNormalizedImages (or another value than none). It returns text and layoutText, the second with positions. A text merge skill can then fold image text back into the document’s own content. The two halves, from Learn’s OCR page:
  • textindexer-and-ocr-skill.jsontype it yourself
    // In the INDEXER definition: crack images out of files so a skill can read them
    "parameters": {
        "configuration": {
            "dataToExtract": "contentAndMetadata",
            "imageAction": "generateNormalizedImages"
        }
    }
    
    // In the SKILLSET definition: the OCR skill, one run per normalized image
    {
        "@odata.type": "#Microsoft.Skills.Vision.OcrSkill",
        "context": "/document/normalized_images/*",
        "defaultLanguageCode": null,
        "inputs": [
            { "name": "image", "source": "/document/normalized_images/*" }
        ],
        "outputs": [
            { "name": "text", "targetName": "myText" },
            { "name": "layoutText", "targetName": "myLayoutText" }
        ]
    }
  • SkillWhat it producesChoose it when
    OCR skillPlain and positioned text from images, including images embedded in PDFsScanned pages and photos where the words are what matter
    Document Layout skillMarkdown or text sections following the layout, with optional image extraction and location metadata, from the Azure Document Intelligence layout modelStructured PDFs and Office files with headings, tables and figures
    Azure Content Understanding skillChunked text sections with tables and figures in Markdown, cross-page tables kept whole, and (preview) AI descriptions of imagesYou want LLM-friendly chunks and figure descriptions; Learn says it costs less than Document Layout
  • Billing is where people trip. OCR and Document Layout are free for a small number of documents per indexer per day (20 at the time of writing); beyond that you attach a billable Foundry resource to the skillset. The keyless way is #Microsoft.Azure.Search.AIServicesByIdentity, with the search service’s managed identity holding Cognitive Services User on the Foundry resource. The Content Understanding skill has no free allowance. Utility skills such as Text Split and Text Merge are not billable.
  • THE EXAM LINE

    OCR needs imageAction set to generateNormalizedImages and reads /document/normalized_images/*. Beyond the free daily allowance, attach a Foundry resource to the skillset, keylessly with AIServicesByIdentity. The Content Understanding skill has no free allowance.

  • Check regions before you build. Learn’s attach page asks for the Foundry resource and the search service in the same region, while the Document Layout page lifts that requirement for Microsoft Entra ID connections. Choosing the same region avoids the question.
OCR finds words, layout finds structure, Content Understanding finds meaning in structure. Pick the least that answers your users.
21:00 – 38:00
Hands-on: build the pipeline over your own mixed files
  • Gather six to ten files you are allowed to use: a text PDF, a scanned page or a photo of printed text (JPG or PNG), a Word or PowerPoint file, and one short audio or video clip. Nothing confidential. In rg-ai103, create a storage account (the portal is fine) with a container called ingest, and upload the document and image files. Deploy an embedding model such as text-embedding-3-small on your Foundry resource, using a deployment type your region offers.
  • Create the search service and its permissions. Learn’s vector quickstart requires the Basic tier or higher for managed identity support, and the tier page lists managed identities for outbound access among the features the Free tier lacks.
  • bashsearch-setup.shtype it yourself
    RG="rg-ai103"
    SEARCH_NAME="<your-search-service-name>"
    STORAGE_NAME="<your-storage-account-name>"
    ACCOUNT_NAME="<your-foundry-resource-name>"
    BASE="/subscriptions/<subscription-id>/resourceGroups/$RG/providers"
    FOUNDRY_SCOPE="$BASE/Microsoft.CognitiveServices/accounts/$ACCOUNT_NAME"
    
    # Basic: the lowest tier with managed identity support. Billed hourly until deleted.
    az search service create --name $SEARCH_NAME --resource-group $RG \
        --sku basic --partition-count 1 --replica-count 1 \
        --identity-type SystemAssigned
    
    # Object IDs: the search service's identity, and you
    SEARCH_MI=$(az ad sp list --display-name "$SEARCH_NAME" --query [].id --output tsv)
    ME=$(az ad signed-in-user show --query id --output tsv)
    
    # Search identity: read blobs, call the embedding model, bill skills without a key
    az role assignment create --assignee-object-id $SEARCH_MI --assignee-principal-type ServicePrincipal \
        --role "Storage Blob Data Reader" --scope "$BASE/Microsoft.Storage/storageAccounts/$STORAGE_NAME"
    az role assignment create --assignee-object-id $SEARCH_MI --assignee-principal-type ServicePrincipal \
        --role "Cognitive Services OpenAI User" --scope $FOUNDRY_SCOPE
    az role assignment create --assignee-object-id $SEARCH_MI --assignee-principal-type ServicePrincipal \
        --role "Cognitive Services User" --scope $FOUNDRY_SCOPE
    
    # You: manage search objects and query the index; call Content Understanding
    for ROLE in "Search Service Contributor" "Search Index Data Contributor" "Search Index Data Reader"; do
        az role assignment create --assignee-object-id $ME --role "$ROLE" \
            --scope "$BASE/Microsoft.Search/searchServices/$SEARCH_NAME"
    done
    az role assignment create --assignee-object-id $ME --role "Cognitive Services User" --scope $FOUNDRY_SCOPE
  • Switch the search service to role-based access: Settings → Keys, then Role-based access control or Both. Learn warns that on the default (API keys), every role-based request is denied. Then run cu_to_markdown.py on your clip and upload the .md file to the same container.
  • Now the wizard. On the search service’s Overview page select Import data, then RAG. Connect to your storage account and container using the system-assigned managed identity. In Vectorize your text, choose the kind whose list shows your Foundry resource and embedding deployment, authenticate with the system-assigned identity, and acknowledge the billing notice. In Vectorize and enrich your images, choose Extract text from images. Leave semantic ranking optional, schedule it Once, set an object name prefix and create. The wizard builds a data source, an index, a skillset and an indexer.
  • When the first run has finished, prove what was built and how it went, keyless, with azure-search-documents (pip install azure-search-documents). Set AZURE_SEARCH_SERVICE_ENDPOINT (the readme’s name) and AZURE_SEARCH_INDEXER_NAME (mine) to the indexer name shown under Indexers.
  • pythoncheck_pipeline.pytype it yourself
    import os
    import time
    from azure.identity import DefaultAzureCredential
    from azure.search.documents.indexes import SearchIndexerClient
    
    client = SearchIndexerClient(
        endpoint=os.environ["AZURE_SEARCH_SERVICE_ENDPOINT"],  # https://<service-name>.search.windows.net
        credential=DefaultAzureCredential(),
    )
    name = os.environ["AZURE_SEARCH_INDEXER_NAME"]            # my variable: the indexer the wizard made
    
    # What was built: data source, target index, and every skill in the skillset
    indexer = client.get_indexer(name)
    print(f"Data source: {indexer.data_source_name} -> index: {indexer.target_index_name}")
    for skill in client.get_skillset(indexer.skillset_name).skills:
        print(f"  skill: {skill.odata_type}  context: {skill.context}")
    
    # Run again to pick up new files (only once the previous run has finished)
    client.run_indexer(name)
    time.sleep(90)
    
    # How the latest run went, item by item
    status = client.get_indexer_status(name)
    last = status.last_result
    print(f"Indexer: {status.status}")
    if last:
        print(f"Last run: {last.status}  items: {last.item_count}  failed: {last.failed_item_count}")
        for error in last.errors:
            print(f"  ERROR   {error.key}: {error.error_message}")
        for warning in last.warnings:
            print(f"  WARNING {warning.key}: {warning.message}")
    WHAT YOU SHOULD SEE
    Data source: <data source name> -> index: <index name>
      skill: <text split skill type>  context: <...>
      skill: <embedding skill type>  context: <...>
      skill: #Microsoft.Skills.Vision.OcrSkill  context: <...>
      <any other skills the wizard added>
    Indexer: running
    Last run: success  items: <your file count>  failed: 0
    (if the last run says inProgress, wait and run the script again)
  • Read the output like an auditor: OCR, split and embedding skills listed; item_count matching your files; each error naming the file (key) and the reason. The portal’s execution history shows the same. If a run fails, fix one thing, rerun, and keep both outputs.
Upload the Markdown before the first run, or run the indexer again. The van only collects what is in the postbox.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
Document crackingconceptThe first indexer stage: opening files and extracting their content.Learn →
imageActionparameterIndexer setting that extracts images from files so skills can read them.Learn →
generateNormalizedImagessettingThe imageAction value that produces normalized images for the OCR skill.Learn →
Microsoft.Skills.Vision.OcrSkillfeatureThe OCR skill type, which reads printed and handwritten text in images.Learn →
Document Layout skillfeatureSkill that turns documents into layout-aware Markdown or text sections.Learn →
Azure Content Understanding skillfeatureSkill that chunks documents with tables and figures in Markdown; no free daily allowance.Learn →
Import dataportalThe search service Overview command that starts the wizard building a whole pipeline.Learn →
Storage Blob Data ReaderroleRole the search service’s identity needs to read blobs.Learn →
Cognitive Services OpenAI UserroleRole a managed identity needs to send text to the embedding model.Learn →
Search Service ContributorroleSearch role that can create indexes, indexers and skillsets and run indexers.Learn →
SearchIndexerClientclassPython client for data sources, skillsets and indexers.Learn →
get_indexer_statusmethodReturns an indexer’s current status and execution history.Learn →
prebuilt-audioSearchconceptContent Understanding RAG analyzer that transcribes and summarises conversations.Learn →
prebuilt-videoSearchmodelContent Understanding RAG analyzer giving transcripts and descriptions per video segment.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Skillset

A reusable set of skills attached to an indexer that enrich content during indexing.

Pull model

Indexing in which the search service fetches content itself, instead of your code pushing documents in.

Self-Check — Before You Submit
1. A manufacturer indexes image-only scanned delivery notes from Blob Storage, but searches never return text from them. What is most likely missing?
  • A. A semantic ranking configuration
  • B. imageAction set to generateNormalizedImages, with the OCR skill in the skillset
  • C. A higher replica count
  • D. A Document attacks guardrail
B. imageAction set to generateNormalizedImages, with the OCR skill in the skillset — Scanned pages hold their text inside images, which is read only when the indexer extracts normalized images and the OCR skill processes them. Semantic ranking re-orders results but cannot find words that were never extracted.
2. A training team wants recorded webinars searchable, with transcripts and per-segment descriptions, in the same index as their PDFs. What is the best approach?
  • A. Point the blob indexer at the MP4 files and add the OCR skill
  • B. Add the Text Split skill to the video files
  • C. Analyze the videos with prebuilt-videoSearch, store the Markdown, and index it
  • D. Turn on semantic ranking
C. Analyze the videos with prebuilt-videoSearch, store the Markdown, and index it — prebuilt-videoSearch produces transcripts and segment descriptions as RAG-ready Markdown, which the indexer can then ingest. OCR is tempting but reads text in images only; it cannot transcribe speech.
3. Your indexer must read blobs through the search service’s system-assigned managed identity, but the service is on the Free tier. What does Learn say?
  • A. Move to Basic or higher: the Free tier lacks managed identities for outbound access
  • B. It works once Role-based access control is enabled
  • C. Assign Storage Blob Data Contributor instead of Reader
  • D. Use the Document Layout skill instead
A. Move to Basic or higher: the Free tier lacks managed identities for outbound access — The tier page lists managed identities for outbound access among the features the Free tier lacks, and the vector quickstart requires Basic or higher. Enabling role-based access control is tempting, but it governs inbound requests to search, not the identity search uses outbound.

Today’s Assignment

Build an ingestion pipeline in Azure AI Search over your own mixed files (documents, an image and an audio or video clip), with OCR and integrated vectorization, and prove each run with indexer status.
  • List your files (type, size and what is in them; nothing confidential) and explain which path each takes: blob indexer only, OCR, or Content Understanding first.
  • Build the pipeline as in the session: storage, a Basic search service with its identity and roles, Import data (RAG) with Extract text from images, and the Markdown from cu_to_markdown.py. Record the exact names of the data source, index, skillset and indexer the wizard created.
  • Run check_pipeline.py. Explain each skill in the list and interpret the run: items processed, failures, warnings, and what you changed if something failed.
  • Search the index (in the portal or with code, as in Session 7) with one query that only the scanned image or the recording can answer, and explain what the result proves.
  • In 300–700 words, say what you would change for production: OCR, Document Layout or the Content Understanding skill; billing attachment; schedule; and the Session 21 security settings you would apply.
  • Say what you kept for Sessions 26 and 27 and what you deleted; if you are pausing the course, delete the search service the same day. Never include keys, admin keys, tokens or connection strings.
Evidence to include
  • The output of check_pipeline.py showing the skills and the last run’s status and item counts.
  • A screenshot of the indexer’s execution history in the portal.
  • The first lines of one Markdown file produced by Content Understanding.
  • Your query and its result, showing content that came from OCR or from the recording.
Names your answer must use exactly
imageAction generateNormalizedImages Microsoft.Skills.Vision.OcrSkill Import data Storage Blob Data Reader SearchIndexerClient get_indexer_status
Never include a key, a connection string or a token. Crop them out of every screenshot. If one is exposed, the letter will tell you to rotate it.
How this is marked — the rubric
The pipeline runs over genuinely mixed files, with OCR and vectorization in the skillset and the Content Understanding Markdown indexed35
The explanation of each file’s path, each skill and the run results is correct, including any failure and its fix25
Uses the exact names from this session’s names box for settings, skills, roles and SDK members15
Evidence: script output, execution history, a Markdown excerpt and the query result are shown with no keys; the clean-up decision for the search service is stated25
Total · pass mark 70100
Submitting session 25
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 25 (the template has it ready, under the Course line), or the submission is refused. Upload the Word file (.docx), or switch to the Google Doc tab and paste your document’s share link.
Marked against the rubric above and returned as a written letter by email; the mark appears on your progress page. Do the self-check above before you submit.
ASSESSMENT NOW OPEN

Pop Quiz 4 — Security and Ingestion opens now. Sit it before the next session. It is how you find out what has stuck, and practice papers may be resat as often as you like.

8 questions · 12 minutes · covers Sessions 21–25 · pass mark 70
Sit Pop Quiz 4 →Closed book, no notes, and Coach Ritchie switches himself off while you sit it.
Skills Covered
Indexers and data sourcesSkillsets and OCRContent Understanding for audio and videoIntegrated vectorizationIndexer run evidence
Tick it once you have submitted.Next: Session 26 →
← Back to course home