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 2 OF 8 · GENERATIVE AI INSIDE APPLICATIONS · SESSIONS 6–10
This week’s assessments: Pop Quiz 2 — Generative Applications after Session 10
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+
B · Implement generative AI and agentic solutions · 30–35%SESSION 6
Tuning generation: prompt engineering and model parameters
Session 6 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Tune generation behavior, such as prompt engineering and adjusting model parameters

Why This Matters

A model that is right most of the time is not good enough for an app a hospital or a bank will use. The difference usually comes from two dials you control: the prompt and the model parameters. AI-103 tests both, and it tests the trap that catches experienced developers: reasoning models reject parameters that older chat models accept, and they want a different style of prompt. Today you run the same task under different techniques and settings, record what changed, and learn which parameter belongs to which API and which model family. That record is the start of every serious tuning job.

IN YOUR SUBSCRIPTION
Creates
Two model deployments on your Foundry resource (Microsoft.CognitiveServices/accounts/deployments): one non-reasoning chat model and one reasoning model, both Global Standard. No other resource. Session 5 deleted its deployments, so you create these fresh.
Cost
Billed per token on both deployments. The scripts send about a dozen short requests. Learn’s reasoning page warns that reasoning tokens are billed as output tokens, so a request that thinks longer costs more even when the visible answer is short. That is why every call here sets an output cap. Check the Microsoft Foundry pricing page for both models before you run anything.
Clean-up
When your evidence is saved, delete both deployments: Foundry portal → Build → Models → open each deployment → delete; or az cognitiveservices account deployment delete --deployment-name <name> -n <resource> -g rg-ai103. Keep rg-ai103 and the project.
The 45-Minute Session
7:00 – 16:00
Prompt engineering techniques, and when they apply
  • TechniqueWhat you doWhy it helps
    Clear instructions, placed firstState the task, the audience and the limits at the startThe model knows the job before it reads the content
    Few-shot learningInclude one or more input and output pairs as examplesShows the format and tone more reliably than describing them
    Repeat instructions at the endRestate the key rule after the contentCounters recency bias: the end of a prompt can carry more weight than the beginning
    Prime the output (a cue)End the prompt with the first words of the answer, such as Update:Pushes the reply into the form you want
    Break the task down / chain of thought promptingAsk for smaller steps, or for the steps to be shownHelps non-reasoning models on multi-step problems
    Specify the output structureSay exactly what shape to return, for example a table or fixed JSON keysMakes output predictable and easier to parse
    Provide grounding contextGive the model the data to answer from, and tell it to use only thatLearn calls it one of the most effective ways to get reliable answers
  • On grounding, next session’s topic, Learn’s advice is short: unless the use case is purely creative, provide grounding data, as close as possible to the final form of the answer.
  • THE EXAM LINE

    Learn’s prompt engineering techniques are not recommended for reasoning models such as gpt-5 and the o-series. Give reasoning models a clear goal, firm constraints and an explicit output contract instead of prescribing every intermediate step.

  • Here is the trap. The techniques page says they aren’t recommended for reasoning models like gpt-5 and the o-series, and its chain-of-thought section warns that extracting a reasoning model’s reasoning by any method other than the reasoning summary parameter isn’t supported and may breach the Acceptable Use Policy. Never write “think step by step” to a gpt-5 deployment.
  • Illustrative: a Lagos bank’s complaints assistant. On a GPT-4.1 deployment, add two example replies and repeat “never promise a refund” at the end. On gpt-5, state the goal, the rule and the output shape once, and stop.
A good prompt reads like a good brief to a colleague: who you are, what you want, what good looks like, and what to do when you are not sure.
25:00 – 33:00
Chat Completions only: stop sequences and penalties
  • Some parameters exist only in the Chat Completions API. The Responses API reference lists temperature, top_p, max_output_tokens, instructions, reasoning and text, but not stop or the penalties. So to use those, you call chat.completions.create through the OpenAI SDK route you built in Session 5.
  • stop: the API reference describes up to 4 sequences where the API will stop generating further tokens, and the returned text does not contain the stop sequence. frequency_penalty and presence_penalty each take a number between -2.0 and 2.0: positive frequency values reduce verbatim repetition, and positive presence values push the model towards new topics.
  • Length has a naming trap too. In Chat Completions, max_tokens is now deprecated in favour of max_completion_tokens, which the reference defines as an upper bound that includes visible output tokens and reasoning tokens.
  • pythonchat_params.pytype it yourself
    import os
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider
    from openai import OpenAI
    
    token_provider = get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default")
    client = OpenAI(base_url=os.environ["FOUNDRY_OPENAI_BASE_URL"], api_key=token_provider)
    
    messages = [
        {"role": "system", "content": "You list practical ideas as a numbered list."},
        {"role": "user", "content": "Give ideas to cut paper use in a public agency office."},
    ]
    settings = [
        {},                                                    # the model's defaults
        {"stop": ["4."]},                                      # stop before item 4
        {"frequency_penalty": 1.5, "presence_penalty": 1.5},   # discourage repetition
    ]
    for extra in settings:
        completion = client.chat.completions.create(
            model=os.environ["FOUNDRY_MODEL_NAME"],            # the NON-reasoning deployment
            messages=messages,
            max_completion_tokens=200,
            **extra,
        )
        print(extra, "->", completion.choices[0].message.content, "\n")
    WHAT YOU SHOULD SEE
    {} -> 1. ... 2. ... 3. ... 4. ... (as many items as fit in 200 tokens)
    {'stop': ['4.']} -> 1. ... 2. ... 3. ...
    {'frequency_penalty': 1.5, 'presence_penalty': 1.5} -> <a list with fewer repeated words and more varied ideas>
  • The second answer should end before item 4, without the 4. itself; the third should repeat itself less. If a penalty makes the list odd, set it lower.
A stop sequence is a cheap, exact guard. A penalty is a blunt nudge. Use the guard when you can.
40:00 – 45:00
Wrap-up: what to write down, the assignment, clean-up
  • Write down: the prompt components; which techniques Learn does not recommend for reasoning models; the ranges for temperature, top_p and the penalties; which parameters exist only in Chat Completions; max_tokens versus max_completion_tokens versus max_output_tokens; and the two spellings of reasoning effort.
  • Your assignment is a tuned prompt from your own work, with a before-and-after comparison and the exact parameters you changed.
  • Clean-up, once your evidence is saved: delete both deployments (Build → Models, or the CLI). Next session needs a chat deployment again, plus an embedding model.
  • If an error does not match the table, send me the message, model and parameter through “Ask R. Wingo” today.
Tuning is an experiment, not an opinion. The table of runs is the result; the paragraph under it is the conclusion.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
system messageconceptInstructions and context given to the model to guide its responses; sent first in chat APIs.Learn →
few-shot learningconceptIncluding example input and output pairs in the prompt to show the model what you want.Learn →
recency biasconceptThe tendency for the end of a prompt to influence output more than the beginning.Learn →
Chain of thought promptingconceptAsking a non-reasoning model to proceed step by step and show the steps; not for reasoning models.Learn →
grounding dataconceptData you give the model to draw its answer from, for reliable, up-to-date responses.Learn →
temperatureparameterSampling randomness, from 0 to 2; lower is more focused. Not supported by reasoning models.Learn →
top_pparameterNucleus sampling, an alternative to temperature; change one or the other, not both.Learn →
max_output_tokensparameterResponses API cap on generated tokens, including reasoning tokens.Learn →
max_completion_tokensparameterChat Completions cap on generated tokens, including reasoning tokens; replaces the deprecated max_tokens.Learn →
stopparameterChat Completions: up to four sequences at which generation stops.Learn →
frequency_penaltyparameterChat Completions, -2.0 to 2.0; positive values reduce verbatim repetition.Learn →
presence_penaltyparameterChat Completions, -2.0 to 2.0; positive values push the model towards new topics.Learn →
reasoning_effortparameterChat Completions setting for how much a reasoning model reasons; Responses uses reasoning={"effort": ...}.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Reasoning model

A model, such as the GPT-5 series or the o-series, that spends reasoning tokens before answering and takes effort settings instead of sampling settings.

Output contract

A precise statement in the system message of the shape the answer must take, such as JSON with fixed keys.

Self-Check — Before You Submit
1. A developer moves a working prompt from a GPT-4.1 deployment to a gpt-5 deployment. The request includes temperature=0.2 and the prompt says “think step by step”. What should they change?
  • A. Nothing; both settings work the same on every model
  • B. Raise temperature to 1.0 and keep the prompt
  • C. Remove temperature, drop the step-by-step instruction, and use reasoning effort with an output cap
  • D. Replace temperature with top_p=0.2
C. Remove temperature, drop the step-by-step instruction, and use reasoning effort with an output cap — Reasoning models such as gpt-5 do not support temperature or top_p, and Learn does not recommend chain-of-thought prompting for them. Swapping temperature for top_p is tempting, but top_p is rejected in the same way.
2. A public agency’s app must end its generated list before a fourth item and must reduce repeated phrases. The team uses the Responses API. What is true?
  • A. stop and frequency_penalty are Chat Completions parameters, so the team calls chat.completions.create for this
  • B. The Responses API accepts stop and frequency_penalty directly
  • C. max_output_tokens=3 guarantees exactly three items
  • D. temperature=0 removes all repetition
A. stop and frequency_penalty are Chat Completions parameters, so the team calls chat.completions.create for this — The Responses API reference does not list stop or the penalty parameters; they belong to Chat Completions. An output cap limits tokens, not list items, so it would cut the text mid-sentence rather than stop cleanly.
3. Which parameter caps the total tokens a reasoning model generates in a Chat Completions request, including its reasoning tokens?
  • A. max_tokens
  • B. top_logprobs
  • C. presence_penalty
  • D. max_completion_tokens
D. max_completion_tokens — max_completion_tokens is the Chat Completions cap and covers reasoning and visible tokens. max_tokens is deprecated and is not supported by reasoning models, which is exactly why this question is a common trap.

Today’s Assignment

Tune a prompt for a real task from your work, prove the improvement with a before-and-after comparison, and state exactly which techniques and parameters you changed.
  • Choose one repeated text task from your work (a reply, a summary, a classification) and say in two sentences what “good” output looks like.
  • Write a before prompt as a colleague would first write it. Then write an after version that uses at least three techniques from this session (for example a system message with an output contract, few-shot examples, a cue, grounding data) and name each one.
  • Run both prompts on your non-reasoning deployment at the same settings, at least twice each. Then change one parameter at a time (temperature or top_p, then stop or a penalty in Chat Completions) and record the effect.
  • Run the after prompt on your reasoning deployment at two reasoning effort levels, with an output cap, and record the reasoning token counts. Explain what you removed from the prompt for this model, and why.
  • Present a table of runs: prompt version, model, every parameter and value, and a short verdict. Then write 300–700 words on what worked, what did not, and which settings you would ship.
  • Include your code with endpoints and deployment names read from environment variables. Never include keys, secrets or credentials; crop endpoints out of screenshots. Delete both deployments and say that you did.
Evidence to include
  • Terminal output for the before and after runs, and for each parameter change, matching your table.
  • Terminal output from the reasoning deployment showing the effort level and reasoning token count for each run.
  • The error message you received when you sent temperature to the reasoning deployment, pasted as text or a screenshot.
Names your answer must use exactly
system message few-shot learning temperature max_completion_tokens max_output_tokens reasoning_effort
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 after prompt is a real improvement, uses at least three named techniques correctly, and avoids chain-of-thought prompting on the reasoning model30
Parameters are changed one at a time, each change is recorded with its exact name and value, and the conclusions follow from the runs25
Uses the exact parameter and technique names from this session’s names box, including the right name for each API20
Evidence: terminal output supports every row of the table, including reasoning token counts and the rejected-parameter error, with no keys or endpoints visible and clean-up stated25
Total · pass mark 70100
Submitting session 6
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 6 (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
Prompt engineering techniquesSystem messagesSampling parametersStop sequences and penaltiesReasoning effort and output caps
Tick it once you have submitted.Next: Session 7 →
B · Implement generative AI and agentic solutions · 30–35%SESSION 7
Retrieval-augmented generation (RAG) in an application
Session 7 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Implement retrieval-augmented generation (RAG) in an application

Why This Matters

Most AI apps that earn their keep answer questions about an organisation’s own content: policies, manuals, contracts, case notes. A model cannot know those, so you retrieve the right passages and hand them over with the question. That is retrieval-augmented generation, and AI-103 expects you to build it, not just define it: chunks, embeddings, a hybrid query, grounded instructions and citations. It also expects you to know which RAG approaches are current, because one well-known option is being retired. Today you build a small RAG app in Python over your own documents, and you pay for a search service by the hour, so you also delete it.

IN YOUR SUBSCRIPTION
Creates
An Azure AI Search service (Microsoft.Search/searchServices) on the Basic tier in rg-ai103, with one index; two model deployments on your Foundry resource (Microsoft.CognitiveServices/accounts/deployments): a chat model and text-embedding-3-small; and two role assignments for your own account on the search service.
Cost
Billed. Basic is part of the Dedicated pricing model: Learn says you pay a fixed hourly rate for the service tier, the service runs continuously for its whole lifetime, and the search service is billed for as long as it exists, whether or not you query it. For the Basic rate, read the Azure AI Search pricing page (azure.microsoft.com/en-us/pricing/details/search) or the pricing calculator. The chat and embedding deployments are billed per token; see the Microsoft Foundry pricing page.
Clean-up
Learn is explicit: to stop billing entirely you must delete the service, and deleting it permanently deletes its indexes. When your evidence is saved, run az search service delete --name <your-search-name> --resource-group rg-ai103 --yes, or in the Azure portal open the search service and select Delete. Then delete both deployments (Build → Models, or az cognitiveservices account deployment delete). Keep rg-ai103, the project and your build_index.py: Session 10 recreates the service in minutes.
The 45-Minute Session
7:00 – 15:00
Chunks, embeddings and citations
  • Chunking. Long documents are split so each piece fits the model’s input limit and carries one idea. Learn’s chunking page recommends starting at 512 tokens (about 2,000 characters) with a 25% overlap, 128 tokens, so no sentence loses its context at a boundary. Inside an indexer pipeline, the Text Split skill does this; today you chunk by hand, one paragraph per chunk.
  • Embeddings. An embedding model turns text into a vector so that similar meanings sit close together. You will use text-embedding-3-small, which Learn lists with 1,536 output dimensions. At the time of writing, Learn’s embeddings page gives 8,192 tokens as the maximum input for current embedding models. The index’s vector field must declare the same number of dimensions as the model produces.
  • A routing detail that catches people: Learn’s SDK overview says to use the OpenAI SDK endpoint (/openai/v1) for embeddings, because the project endpoint used by the Foundry SDK doesn’t currently route embedding requests. So embeddings.create goes through the same OpenAI client you built in Session 5, while the chat call can stay on get_openai_client().
  • THE EXAM LINE

    Embed the query with the same model you used to embed the content, and match the vector field’s dimensions to it. A hybrid query sends search_text and a vector query together; citations come from ids you pass with each chunk.

  • Citations. The model can only cite what it can see. So every chunk gets a stable id, the app prints it next to the text in the prompt, and the instructions demand it after each fact. Learn’s RAG quickstart uses the same grounding instructions: answer only with the facts in the sources, and say you don’t know if they are not enough.
  • Illustrative: an HR assistant for a hospital trust. The chunk leave-2 says staff may take five days of compassionate leave. The answer “five days [leave-2]” lets a nurse check the source in one click.
An answer without a citation is an opinion. In a regulated organisation, an opinion is a liability.
23:00 – 31:00
Build a small vector index from your own documents
  • Choose three to eight paragraphs from a document you are allowed to use: a policy, a procedure, a product sheet. Nothing confidential, nothing personal. Paste each paragraph into its own chunk with an id and a title.
  • The script below follows the shape of Learn’s Python vector search quickstart: SearchIndexClient creates the index, a SearchField declares vector_search_dimensions and a vector profile, HnswAlgorithmConfiguration names the algorithm, and SearchClient uploads the documents. The only addition is that we compute each vector with your embedding deployment instead of loading precomputed ones.
  • pythonbuild_index.pytype it yourself
    import os
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider
    from azure.search.documents import SearchClient
    from azure.search.documents.indexes import SearchIndexClient
    from azure.search.documents.indexes.models import (
        SearchIndex, SimpleField, SearchableField, SearchField, SearchFieldDataType,
        VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile)
    from openai import OpenAI
    
    credential = DefaultAzureCredential()
    endpoint, index_name = os.environ["SEARCH_ENDPOINT"], os.environ["SEARCH_INDEX"]
    embedder = OpenAI(base_url=os.environ["FOUNDRY_OPENAI_BASE_URL"],
                      api_key=get_bearer_token_provider(credential, "https://ai.azure.com/.default"))
    
    fields = [
        SimpleField(name="id", type=SearchFieldDataType.String, key=True, filterable=True),
        SearchableField(name="title", type=SearchFieldDataType.String),
        SearchableField(name="content", type=SearchFieldDataType.String),
        SearchField(name="content_vector", type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
                    searchable=True, vector_search_dimensions=1536,  # text-embedding-3-small
                    vector_search_profile_name="my-vector-profile"),
    ]
    vector_search = VectorSearch(
        algorithms=[HnswAlgorithmConfiguration(name="my-hnsw-vector-config-1")],
        profiles=[VectorSearchProfile(name="my-vector-profile",
                                      algorithm_configuration_name="my-hnsw-vector-config-1")])
    SearchIndexClient(endpoint=endpoint, credential=credential).create_or_update_index(
        SearchIndex(name=index_name, fields=fields, vector_search=vector_search))
    
    # Your own chunks: one paragraph each, with a stable id you can cite
    chunks = [
        {"id": "leave-1", "title": "Leave policy", "content": "<a paragraph from your document>"},
        {"id": "leave-2", "title": "Leave policy", "content": "<the next paragraph>"},
    ]
    for chunk in chunks:
        chunk["content_vector"] = embedder.embeddings.create(
            model=os.environ["EMBEDDING_DEPLOYMENT"], input=chunk["content"]).data[0].embedding
    result = SearchClient(endpoint=endpoint, index_name=index_name,
                          credential=credential).upload_documents(documents=chunks)
    print(f"Uploaded {len(result)} chunks to {index_name}")
    WHAT YOU SHOULD SEE
    Uploaded 2 chunks to ai103-policies
  • Forbidden usually means the role assignment has not taken effect yet: wait and retry. A vector error usually means the deployment is not text-embedding-3-small or the field does not say 1536.
  • Vector profiles, the HNSW settings and exhaustive search get their own session later (Session 26). Today they are the minimum that makes vector search work.
Good chunks are boring: one idea each, a clear title, a stable id. Clever chunks are hard to cite.
40:00 – 45:00
Wrap-up: write it down, then delete the search service
  • Write down: retrieve, augment, generate; the chunking starting point; text-embedding-3-small and its dimensions; why embeddings use the OpenAI SDK endpoint; SearchIndexClient, SearchClient, VectorizedQuery; the two search roles; and the retirement of Azure OpenAI On Your Data.
  • Your assignment is a working RAG script over your own documents, with a grounded answer and its citations.
  • Clean-up, today: the search service is billed while it exists. Once your screenshots are saved, run az search service delete (or Delete on the service in the portal), then delete both deployments. Session 10 recreates the service with the same commands and your build_index.py.
  • If you keep it because you will reach Session 10 within a day, write that decision down. That is a cost you chose, not one you forgot.
The most expensive line in this session is the one you did not run: the delete.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
Azure OpenAI On Your DatafeatureManaged grounding of GPT-4o models on your data; deprecated and approaching retirement.Learn →
text-embedding-3-smallmodelAn embedding model sold by Azure, producing 1,536-dimension vectors.Learn →
embeddings.createmethodThe OpenAI SDK call that turns input text into an embedding vector.Learn →
Text Split skillfeatureThe built-in indexer skill that chunks text by pages or sentences.Learn →
azure-search-documentspackageThe Python package for Azure AI Search index management and queries.Learn →
SearchIndexClientclassCreates, updates and deletes search indexes.Learn →
SearchClientclassUploads documents to one index and queries it.Learn →
VectorizedQueryclassA vector query built from an embedding you computed; pass it in vector_queries.Learn →
vector_search_dimensionsparameterThe number of dimensions a vector field holds; must match the embedding model.Learn →
Search Index Data ContributorroleData-plane role to load, change and query documents in an index.Learn →
Search Service ContributorroleRole to create and manage indexes and other objects on a search service.Learn →
Dedicated pricing modeltierProvisioned search capacity, including Basic, billed at a fixed hourly rate while the service exists.Learn →
az search service deletecliDeletes a search service and its data; the only way to stop Dedicated billing.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Chunk

A small, self-contained piece of a larger document, sized to fit model limits and carry one idea.

Citation

A reference in the answer to the specific retrieved source a fact came from, so a reader can check it.

Self-Check — Before You Submit
1. A logistics firm is starting a new grounded question-answering app over its operating manuals. A consultant proposes Azure OpenAI On Your Data. What should you say?
  • A. It is the recommended approach for all new RAG apps
  • B. It is deprecated and approaching retirement; build RAG over Azure AI Search yourself, or use agentic retrieval or Foundry IQ
  • C. It is the only way to cite sources
  • D. It requires fine-tuning the model on the manuals first
B. It is deprecated and approaching retirement; build RAG over Azure AI Search yourself, or use agentic retrieval or Foundry IQ — Learn marks Azure OpenAI On Your Data as deprecated, with a retirement date, and points to Foundry Agent Service with Foundry IQ. Citations are available in your own RAG code too, so the third option is wrong.
2. Your index was built with text-embedding-3-small. At query time, what must the app do for the vector part of a hybrid query?
  • A. Embed the query with the same embedding model and pass the vector in a VectorizedQuery
  • B. Send the raw query text in the vector field
  • C. Embed the query with the chat model deployment
  • D. Nothing; the semantic ranker creates vectors automatically
A. Embed the query with the same embedding model and pass the vector in a VectorizedQuery — Query and content vectors must come from the same embedding model to be comparable, and VectorizedQuery carries a vector you computed. The semantic ranker re-ranks results; it does not create vectors for your query.
3. You finish a lab with a Basic tier Azure AI Search service and will not use it for two weeks. How do you stop its charges?
  • A. Delete all indexes but keep the service
  • B. Stop the service from the Overview page
  • C. Remove your role assignments
  • D. Delete the search service
D. Delete the search service — Learn says that in the Dedicated pricing model the service runs continuously and the only way to stop billing entirely is to delete it. There is no stop button, and empty indexes do not reduce the hourly rate.

Today’s Assignment

Build a working RAG script over a handful of your own documents that returns grounded answers with citations, and explain how it works.
  • Choose three to eight paragraphs from documents you are allowed to use at work or in an organisation you know. Remove anything personal or confidential. Chunk them one idea per chunk, each with an id and a title.
  • Create the Basic search service and the embedding deployment, assign yourself the two search roles, and build the index with build_index.py adapted to your chunks.
  • Adapt rag.py to your domain. Run three questions: one fully answered by your documents, one partly answered, and one not answered at all. Keep the printed sources and the answers.
  • Check every citation against the retrieved sources and report any that do not match. Say what you changed if the unanswerable question was answered anyway.
  • Explain in 300–700 words: the retrieve, augment and generate steps in your code; how you chunked and why; why the query is embedded with the same model; why embeddings go through the OpenAI SDK endpoint; and which RAG approach you would choose for production and why.
  • Include your full code with every endpoint and name read from environment variables. Never include keys, secrets or credentials; crop endpoints out of screenshots. Delete the search service and both deployments, and say that you did.
Evidence to include
  • Terminal output of build_index.py and of all three rag.py questions, showing the retrieved sources and the cited answers.
  • A screenshot of the search service Overview in the Azure portal showing the Basic pricing tier, and of its Access control (IAM) role assignments for your account.
  • The output of az search service delete, or a screenshot of the resource group without the search service afterwards.
Names your answer must use exactly
SearchIndexClient SearchClient VectorizedQuery text-embedding-3-small Search Index Data Contributor Azure OpenAI On Your Data Dedicated pricing model
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 RAG script works over the student’s own chunks, answers with correct citations, and declines the unanswerable question35
The explanation of chunking, embeddings, the hybrid query and the choice of RAG approach is accurate and specific to this code20
Uses the exact class, model, role and pricing names from this session’s names box20
Evidence: outputs for indexing and all three questions, the tier and role screenshots, and proof the search service was deleted, with no keys or endpoints visible25
Total · pass mark 70100
Submitting session 7
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 7 (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
The RAG patternChunking and embeddingsHybrid queries in PythonGrounded answers with citationsAzure AI Search cost and clean-up
Tick it once you have submitted.Next: Session 8 →
B · Implement generative AI and agentic solutions · 30–35%SESSION 8
Workflows, tool-augmented flows and multistep reasoning
Session 8 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Design workflows, tool-augmented flows, and multistep reasoning pipelines

Why This Matters

A model on its own can only write. The moment an app needs today’s shipment status, a stock level or an approval, the model has to ask your code to fetch or do something, and then carry on with the result. That is a tool-augmented flow, and chained together it becomes a multistep pipeline. AI-103 asks you to design these: when a single call is enough, when to add tools, how the loop works, and which orchestration pattern fits. At work, the design decides whether the flow is safe and traceable or quietly unbounded. Today you build a two-step flow with a real function call, extend it to a guarded loop, and trace every step.

IN YOUR SUBSCRIPTION
Creates
One chat model deployment on your Foundry resource (Microsoft.CognitiveServices/accounts/deployments), Global Standard. The function you call is local Python on your machine; no other Azure resource is created.
Cost
Billed per token. Every step in a tool loop is a separate, billed model request, so a loop costs several calls rather than one. The loop in this session is capped at five steps for that reason. Check the Microsoft Foundry pricing page for your model.
Clean-up
When your trace is saved, delete the deployment: Build → Models → open it → delete; or az cognitiveservices account deployment delete --deployment-name <name> -n <resource> -g rg-ai103. Keep rg-ai103 and the project.
The 45-Minute Session
7:00 – 17:00
A two-step flow with one function call
  • A function tool has four parts in the Responses API: "type": "function", a name, a description the model reads to decide when to call it, and parameters as a JSON schema. Learn’s function calling page limits descriptions to 1,024 characters at the time of writing.
  • The script follows the function calling sample on Learn’s Responses API page, connected through your project with DefaultAzureCredential as in Session 5. The model’s request arrives as an output item of type function_call with a call_id and JSON arguments. You answer with a function_call_output item carrying the same call_id, chained with previous_response_id.
  • pythontool_flow.pytype it yourself
    import json
    import os
    from azure.identity import DefaultAzureCredential
    from azure.ai.projects import AIProjectClient
    
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
                              credential=DefaultAzureCredential())
    openai = project.get_openai_client()
    MODEL = os.environ["FOUNDRY_MODEL_NAME"]
    
    # Illustrative data. In real life this is your shipment system's API.
    SHIPMENTS = {"LG-221": {"status": "held at customs", "hub": "Lagos", "eta_days": 3},
                 "LG-104": {"status": "delivered", "hub": "Abuja", "eta_days": 0}}
    
    def get_shipment_status(tracking_id):
        return SHIPMENTS.get(tracking_id, {"error": "unknown tracking id"})
    
    tools = [{"type": "function", "name": "get_shipment_status",
              "description": "Look up the live status of one parcel by its tracking id",
              "parameters": {"type": "object",
                             "properties": {"tracking_id": {"type": "string"}},
                             "required": ["tracking_id"]}}]
    
    # Step 1: the model decides whether to call the tool, and with which arguments
    response = openai.responses.create(model=MODEL, tools=tools,
                                       input="Where is parcel LG-221, and when will it arrive?")
    tool_outputs = []
    for item in response.output:
        if item.type == "function_call" and item.name == "get_shipment_status":
            args = json.loads(item.arguments)
            result = get_shipment_status(args["tracking_id"])      # YOUR code runs it
            print(f"TRACE 1: {item.name}({args}) call_id={item.call_id} -> {result}")
            tool_outputs.append({"type": "function_call_output",
                                 "call_id": item.call_id, "output": json.dumps(result)})
    
    # Step 2: send the result back; the model writes the answer (skipped if no tool was called)
    if tool_outputs:
        response = openai.responses.create(model=MODEL, previous_response_id=response.id, input=tool_outputs)
    print(f"TRACE 2: {response.output_text}")
    WHAT YOU SHOULD SEE
    TRACE 1: get_shipment_status({'tracking_id': 'LG-221'}) call_id=call_<id> -> {'status': 'held at customs', 'hub': 'Lagos', 'eta_days': 3}
    TRACE 2: Parcel LG-221 is held at customs in Lagos and is expected in about 3 days.
  • Read the two TRACE lines. The first proves the model chose the tool and the arguments. The second proves the answer was built from your function’s result, not invented: the customs status and the three days came from SHIPMENTS.
  • Then ask a question that needs no tool, such as “What does ETA mean?”. The model should answer directly with no function_call item, so the script skips step 2. That is the default tool_choice, auto: the model decides whether to call a tool at all. Learn also documents none, and naming a specific function to force it.
  • Treat arguments as untrusted input. The model wrote them. Validate them the way you would validate a form a stranger filled in.
If the model never calls your tool, rewrite the description before you touch the code. The description is the tool’s job advert.
27:00 – 38:00
Designing pipelines: chains, planners and Foundry workflows
  • A tool loop is one pattern. Multistep reasoning pipelines are often designed as a fixed sequence instead: step 1 extracts, step 2 checks, step 3 drafts. Each step can be a separate model call with its own prompt and even its own model, which is how you mix a small model for extraction with a larger one for drafting.
  • Pattern (Architecture Center name)How it worksChoose it when
    Sequential orchestrationSteps run in a fixed, linear order, each using the previous output; also known as a pipeline or prompt chainingThe process is well defined and each stage has a clear input and output
    Concurrent orchestrationSeveral agents work on the same task at the same time, each from its own specialismIndependent analyses can run side by side and be combined
    Group chat orchestration (including maker-checker)Agents share a conversation; a maker proposes, a checker reviews against criteria, up to an iteration limitWork needs review or debate before it is accepted
    Handoff orchestrationAn agent decides whether to handle the task or pass it to a better-suited agentThe right specialist is only clear once the task is understood
    Magentic orchestrationA manager agent builds a task ledger of goals and subgoals, then tracks it to completionOpen-ended problems with no predetermined plan: the planner/executor shape
  • The planner/executor idea is the last row in miniature: one call writes the plan, other calls or tools carry it out, and the manager checks progress against the plan. For most exam scenarios, choose the simplest row that fits the process described.
  • Foundry also offers workflows: a visual builder, in preview, for declarative sequences that orchestrate agents and business logic, with Sequential, Human in the loop and Group chat templates, if/else logic, variables and YAML definitions. But read the notice at the top of that page: at the time of writing, Microsoft Foundry is retiring workflows on December 1, 2026, and for new workflows Learn says to use Microsoft Agent Framework. Foundry will continue to run YAML workflow definitions deployed as hosted agents.
  • THE EXAM LINE

    For new code-first multi-agent workflows, Learn points to Microsoft Agent Framework (Python package agent-framework). Foundry’s visual workflows are in preview and scheduled for retirement.

  • Agent Framework’s own guidance is a useful exam filter: use an agent when the task is open-ended or conversational and needs autonomous tool use; use a workflow when the process has well-defined steps and you need explicit control over execution order. We build agents from Week 3 and orchestrate them in Session 15.
When a scenario lists the steps in order, it is telling you the pattern. Do not reach for a manager agent to run a checklist.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
tool_choiceparameterControls whether the model may call tools: auto (default), none, or a named function to force.Learn →
function_call_outputparameterResponses API input item that returns your function’s result, matched by call_id.Learn →
call_idparameterThe identifier on a function_call item that your function_call_output must repeat.Learn →
parallel_tool_callsparameterWhether to enable parallel function calling during tool use; true by default in Chat Completions.Learn →
Sequential orchestrationconceptAgents or steps run in a fixed linear order, each using the previous output.Learn →
prompt chainingconceptAnother name for sequential orchestration: one call’s output becomes the next call’s input.Learn →
Magentic orchestrationconceptA manager agent builds and tracks a task ledger for open-ended problems.Learn →
Handoff orchestrationconceptAgents decide whether to handle a task or transfer it to a better-suited agent.Learn →
Human in the loopfeatureWorkflow pattern that asks the user a question and waits for input before continuing.Learn →
Microsoft Agent FrameworktoolOpen-source SDK for agents and code-first multi-agent workflows; Learn’s path for new workflows.Learn →
agent-frameworkpackageThe pip package for Microsoft Agent Framework in Python.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Tool loop

Repeating model calls and tool executions until the model answers in text or a step limit is reached.

Trace

A step-by-step record of each model request, tool call, arguments and result in a flow.

Self-Check — Before You Submit
1. A bank’s assistant must check a customer’s card status in a core system before replying. What happens when the model decides to use the get_card_status function?
  • A. The model connects to the core system and runs the function itself
  • B. Foundry runs the function in the cloud automatically
  • C. The model returns a function call with arguments; the app runs it and sends the result back with the call_id
  • D. The model guesses the status from its training data
C. The model returns a function call with arguments; the app runs it and sends the result back with the call_id — Learn states the model generates function calls and structured arguments, while your application executes the functions and returns results. Nothing runs the function for you, which is exactly what keeps you in control of actions.
2. An invoice process always runs extract, then validate, then draft a summary, in that order. Which orchestration pattern fits best?
  • A. Magentic orchestration
  • B. Sequential orchestration
  • C. Group chat orchestration
  • D. Handoff orchestration
B. Sequential orchestration — Sequential orchestration, also called prompt chaining, runs steps in a predefined linear order. Magentic orchestration is tempting because it plans, but it is meant for open-ended problems with no predetermined plan.
3. A team wants to start building a new multi-agent workflow in Foundry this month. According to Learn, what should they use?
  • A. Microsoft Agent Framework
  • B. The Foundry visual workflow builder, since it is generally available
  • C. Azure OpenAI On Your Data
  • D. A single Chat Completions call with no tools
A. Microsoft Agent Framework — Learn’s workflow page says Foundry is retiring workflows on December 1, 2026, and new workflows should use Microsoft Agent Framework. The visual builder is also still in preview, so the second option is wrong twice.

Today’s Assignment

Build a tool-augmented flow for a task from your own work, with at least one real function call and a step-by-step trace, and explain its design.
  • Choose a task where a model needs live data or an action from a system you know (a stock check, a booking lookup, a ticket status). Write the function in Python; it may return illustrative data, labelled as such.
  • Define the function tool with a clear description and a JSON schema for its parameters. Build the two-step flow from tool_flow.py, then extend it to the guarded loop from loop.py with an allow-list and a step limit.
  • Run three inputs: one that needs one tool call, one that needs several (parallel or sequential), and one that needs no tool. Print a trace line for every model request and every function call.
  • Explain in 300–700 words: how the model, your code and the results interact at each step; why you chose your step limit; which orchestration pattern your flow represents and why a more complex one is not needed; and how you would add an approval before any function that changes data.
  • Include your full code, with endpoints and deployment names read from environment variables. Never include keys, secrets or credentials; crop endpoints out of screenshots. Delete the deployment afterwards and say so.
Evidence to include
  • The complete terminal trace for all three inputs, showing each function name, its arguments, its call_id or step number, and the final answers.
  • A screenshot of the deployment in Build → Models before you deleted it.
Names your answer must use exactly
tool_choice function_call_output call_id Sequential orchestration Microsoft Agent Framework
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 flow works: the tool is called with correct arguments, results are returned by call_id, the loop is bounded and allow-listed, and the no-tool case answers directly35
The design explanation correctly places the flow on the complexity spectrum and names a fitting orchestration pattern with a reason20
Uses the exact parameter, pattern and framework names from this session’s names box20
Evidence: the trace shows every step for all three inputs and the deployment screenshot is included, with no keys or endpoints visible and clean-up stated25
Total · pass mark 70100
Submitting session 8
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 8 (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
Function callingTool loops with guardsParallel function callsOrchestration patternsFoundry workflows and Agent Framework
Tick it once you have submitted.Next: Session 9 →
B · Implement generative AI and agentic solutions · 30–35%SESSION 9
Evaluating models and apps: fabrications, relevance, quality, safety
Session 9 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Evaluate models and apps, including detecting fabrications, relevance, quality, and safety

Why This Matters

Every team says its AI app works. Very few can prove it, and fewer still can prove it has not made something up. AI-103 expects you to evaluate models and apps for fabrications, relevance, quality and safety, using Foundry’s evaluators rather than your own impression. It also expects the current names, and this is an area where Microsoft has changed them: the class names you will meet in older material are now the classic path, and the current Foundry path identifies evaluators differently. Today you run a real evaluation over a small dataset, read the results in code and in the portal, and turn a failing score into a fix.

IN YOUR SUBSCRIPTION
Creates
One judge model deployment on your Foundry resource (Microsoft.CognitiveServices/accounts/deployments), Global Standard; and one evaluation with one run, stored in your Foundry project. No search service is needed: the dataset is typed into the script.
Cost
Billed. AI-assisted evaluators call your judge deployment, and Learn’s portal page says the judge model counts against your Azure OpenAI quota; its tokens are billed like any other request. Three rows with three evaluators is a small job. Risk and safety evaluators use Foundry’s hosted evaluation models; check the Microsoft Foundry pricing page for how evaluation is charged before you scale up a dataset.
Clean-up
Keep the evaluation and its run in the project: they are your evidence. When your screenshots are saved, delete the judge deployment (Build → Models, or az cognitiveservices account deployment delete --deployment-name <name> -n <resource> -g rg-ai103). Keep rg-ai103 and the project.
The 45-Minute Session
8:00 – 15:00
The names changed: classic SDK versus Foundry cloud evaluation
  • Older courses and the exam’s own wording talk about the Azure AI Evaluation SDK. Its package is azure-ai-evaluation, and it exposes evaluator classes such as GroundednessEvaluator and RelevanceEvaluator, run locally with an evaluate() function. Learn now files that how-to under the Foundry (classic) portal and says it isn’t available for the new Foundry portal.
  • The current Foundry path is cloud evaluation through the Foundry SDK you already use. You create an evaluation with openai_client.evals.create(), start it with evals.runs.create(), and name each evaluator with an identifier such as builtin.groundedness inside a TestingCriterionAzureAIEvaluator.
  • Classic (Foundry classic portal)Current (new Foundry)
    Packageazure-ai-evaluationazure-ai-projects with the project’s OpenAI client
    How an evaluator is namedA class: GroundednessEvaluator(model_config)An identifier: evaluator_name="builtin.groundedness"
    Where it runsLocally, with evaluate()In the cloud, as an evaluation and a run in your project
    How fields are wiredcolumn_mappingdata_mapping with {{item.field}} templates
  • THE EXAM LINE

    Recognise both forms: azure-ai-evaluation with classes such as GroundednessEvaluator (classic, local), and cloud evaluation with evals.create, testing_criteria and builtin.groundedness identifiers (current Foundry).

  • A warning from my own reading. Learn’s current pages do not agree on one key. The cloud evaluation how-to passes the judge as initialization_parameters={"model": ...}; the newer agent evaluation how-to and the RAG evaluator page use {"deployment_name": ...}. I follow the newest page. If the service rejects it, try the other and tell me which worked.
When two Learn pages disagree, note both dates, follow the newer one, and write down what you did. That habit will save you on the job.
23:00 – 33:00
Run the evaluation from code
  • The script follows the shape of Learn’s cloud evaluation how-to: a custom data source config with your schema, three testing criteria, evals.create, then evals.runs.create with the rows supplied as file content.
  • pythoneval_run.pytype it yourself
    import os
    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)
    from eval_data import ROWS, SCHEMA
    
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
                              credential=DefaultAzureCredential())
    client = project.get_openai_client()
    judge = os.environ["FOUNDRY_MODEL_NAME"]            # the judge model deployment
    
    testing_criteria = [
        TestingCriterionAzureAIEvaluator(
            type="azure_ai_evaluator", name="groundedness", evaluator_name="builtin.groundedness",
            initialization_parameters={"deployment_name": judge},
            data_mapping={"context": "{{item.context}}", "response": "{{item.response}}"}),
        TestingCriterionAzureAIEvaluator(
            type="azure_ai_evaluator", name="relevance", evaluator_name="builtin.relevance",
            initialization_parameters={"deployment_name": judge},
            data_mapping={"query": "{{item.query}}", "response": "{{item.response}}"}),
        TestingCriterionAzureAIEvaluator(      # safety: hosted evaluation model, no judge needed
            type="azure_ai_evaluator", name="violence", evaluator_name="builtin.violence",
            data_mapping={"query": "{{item.query}}", "response": "{{item.response}}"}),
    ]
    evaluation = client.evals.create(
        name="ai103-s9-rag-eval",
        data_source_config=DataSourceConfigCustom(type="custom", item_schema=SCHEMA),
        testing_criteria=testing_criteria,
    )
    run = client.evals.runs.create(
        eval_id=evaluation.id, name="run-1",
        data_source=CreateEvalJSONLRunDataSourceParam(type="jsonl", source=SourceFileContent(
            type="file_content", content=[SourceFileContentContent(item=row) for row in ROWS])),
    )
    print(f"python eval_results.py {evaluation.id} {run.id}")
    WHAT YOU SHOULD SEE
    python eval_results.py <eval-id> <run-id>
  • Read the data_mapping lines closely: {{item.context}} means “the context field of each row”. A wrong mapping is the most common reason for a score that makes no sense.
  • Now poll for the result and print it, again following the how-to. Copy the command that eval_run.py printed.
  • pythoneval_results.pytype it yourself
    import os
    import sys
    import time
    from pprint import pprint
    from azure.identity import DefaultAzureCredential
    from azure.ai.projects import AIProjectClient
    
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
                              credential=DefaultAzureCredential())
    client = project.get_openai_client()
    eval_id, run_id = sys.argv[1], sys.argv[2]
    
    while True:
        run = client.evals.runs.retrieve(run_id=run_id, eval_id=eval_id)
        if run.status in ("completed", "failed"):
            break
        time.sleep(5)
        print("Waiting for eval run to complete...")
    
    # One output item per row: each evaluator's label, score, passed and threshold
    pprint(list(client.evals.runs.output_items.list(run_id=run_id, eval_id=eval_id)))
    print(f"Status: {run.status}")
    print(f"Report URL: {run.report_url}")
    WHAT YOU SHOULD SEE
    Waiting for eval run to complete...
    [<row 1: groundedness pass, relevance pass, violence pass>,
     <row 2: groundedness FAIL (the roll-over claim is not in the context), relevance pass, violence pass>,
     <row 3: relevance FAIL, violence pass>]
    Status: completed
    Report URL: <a link to this run in the Foundry portal>
  • Learn’s results schema gives every evaluator the same four fields per row: a label (pass or fail), a score on the evaluator’s own scale, passed (whether the score met the threshold) and the threshold itself. run.report_url opens the same results in the Foundry portal.
Save the eval id and the run id with your evidence. They are how you and I find the exact run again.
40:00 – 45:00
Wrap-up: what to write down, the assignment, clean-up
  • Write down: the three lifecycle stages; the evaluator groups; what Groundedness, Relevance, Retrieval and Response Completeness each measure; the classic and current names side by side; evals.create, evals.runs.create, data_mapping, report_url; and the 1–5 and 0–7 scales.
  • Your assignment is an evaluation report on your own RAG answers, with the metrics and the fix they point to.
  • Clean-up: keep the evaluation records; delete the judge deployment once your screenshots are saved.
  • If the run ends as Failed, copy the error from the portal and the run id, and send them through “Ask R. Wingo” the same day.
Evidence beats description, and an evaluation is evidence you can rerun.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
azure-ai-evaluationpackageThe Azure AI Evaluation SDK package for local evaluation; documented under the Foundry (classic) portal.Learn →
GroundednessEvaluatorclassClassic SDK class that scores how well a response is grounded in context.Learn →
RelevanceEvaluatorclassClassic SDK class that scores how well a response addresses the query.Learn →
builtin.groundednesssettingCurrent evaluator identifier for Groundedness in cloud evaluation.Learn →
builtin.relevancesettingCurrent evaluator identifier for Relevance in cloud evaluation.Learn →
builtin.violencesettingRisk and safety evaluator identifier; uses hosted models and a 0 to 7 severity scale.Learn →
Groundedness ProfeaturePreview evaluator that checks strict consistency with context using Azure AI Content Safety.Learn →
Response CompletenessfeaturePreview evaluator for the recall side: does the response miss information in the ground truth?Learn →
TestingCriterionAzureAIEvaluatorclassFoundry SDK class that adds a built-in evaluator, with its data mapping, to an evaluation.Learn →
testing_criteriaparameterThe list of evaluators an evaluation applies to each row.Learn →
data_mappingparameterMaps evaluator inputs to dataset fields using templates such as {{item.response}}.Learn →
evals.createmethodCreates an evaluation definition through the project’s OpenAI client.Learn →
report_urlparameterRun property linking to the evaluation results in the Foundry portal.Learn →
AI red teaming agenttoolSimulates attacks with Microsoft’s PyRIT framework to find safety and security weaknesses before deployment.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Judge model

The deployed model an AI-assisted evaluator uses to read and score each row.

Evaluation dataset

Rows of test cases with named fields, such as query, context and response, that an evaluation scores.

Self-Check — Before You Submit
1. A Lagos bank’s policy assistant sometimes adds loan terms that are not in the retrieved policy text. Which evaluator targets this problem?
  • A. Fluency
  • B. Groundedness
  • C. Response Completeness
  • D. Violence
B. Groundedness — Groundedness measures whether the response aligns with the context without fabricating content. Response Completeness is tempting, but it measures missing information against a ground truth, not invented information.
2. You are writing new code in the current Foundry portal and SDK to add a relevance check to an evaluation. How do you specify the evaluator?
  • A. from azure.ai.evaluation import RelevanceEvaluator, then call evaluate()
  • B. Set temperature=0 on the judge
  • C. Use a TestingCriterionAzureAIEvaluator with evaluator_name="builtin.relevance" in testing_criteria
  • D. Add a stop sequence called relevance
C. Use a TestingCriterionAzureAIEvaluator with evaluator_name="builtin.relevance" in testing_criteria — Current cloud evaluation names built-in evaluators with identifiers such as builtin.relevance inside testing criteria. The RelevanceEvaluator class belongs to the classic azure-ai-evaluation SDK, which Learn now documents only for the classic portal.
3. Which statement about risk and safety evaluators such as builtin.violence is correct?
  • A. They use hosted Foundry evaluation models, score severity from 0 to 7, and need no judge deployment
  • B. They require a judge deployment and score from 1 to 5
  • C. They only run locally with azure-ai-evaluation
  • D. They measure whether the answer matches the ground truth
A. They use hosted Foundry evaluation models, score severity from 0 to 7, and need no judge deployment — Learn’s risk and safety page says these evaluators use hosted models, need no deployment_name, and score severity on a 0 to 7 scale. The 1 to 5 scale belongs to quality evaluators such as Groundedness.

Today’s Assignment

Evaluate answers from your own RAG app for groundedness, relevance and safety, and write a short report on the metrics and the fix they point to.
  • Build a dataset of at least six rows from your Session 7 app: the query, the retrieved context and the app’s response. Your saved Session 7 output gives you the first rows; write further queries against the same saved sources for the rest. Include at least one row you expect to fail groundedness and one you expect to fail relevance, and say which.
  • Run a cloud evaluation with eval_run.py adapted to your rows, using at least builtin.groundedness, builtin.relevance and one risk and safety evaluator. Poll and print the results with eval_results.py.
  • Make one fix the results point to (instructions, retrieval or parameters), produce new responses for the same queries, and run the evaluation again. An instructions or parameter fix needs only the generate step of rag.py, with your saved sources pasted into the input and your judge deployment as the model. A retrieval fix means recreating the Session 7 search service and index with setup-search.sh and build_index.py, then deleting the service again.
  • Write a report of 300–700 words: a table of rows with each evaluator’s label and score before and after; what each evaluator measures; why the failing rows failed; the fix you made and its effect; and how the classic azure-ai-evaluation classes relate to what you used.
  • Include your code with every endpoint and name read from environment variables. Never include keys, secrets or credentials, and do not use personal or confidential data in the dataset. Delete the judge deployment and say so.
Evidence to include
  • The terminal output of eval_results.py for both runs, showing the eval id, run id, status and per-row results.
  • A screenshot of each run in the Foundry portal Evaluation page (or its report URL) showing the metrics, with endpoints cropped.
  • Your before-and-after table, matching the outputs.
Names your answer must use exactly
builtin.groundedness builtin.relevance TestingCriterionAzureAIEvaluator data_mapping evals.create GroundednessEvaluator
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 evaluation runs on the student’s own rows with correct data mappings, and the planted failures are detected and explained30
A fix is chosen from the results, applied, and its effect shown by a second run and an honest comparison25
Uses the exact evaluator identifiers, class, method and package names from this session’s names box, including the classic and current forms20
Evidence: terminal output and portal screenshots show both runs and match the table, with no keys or endpoints visible and clean-up stated25
Total · pass mark 70100
Submitting session 9
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 9 (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
Groundedness and fabricationRelevance and quality evaluatorsSafety evaluatorsCloud evaluation with the Foundry SDKClassic versus current evaluator names
Tick it once you have submitted.Next: Session 10 →
B · Implement generative AI and agentic solutions · 30–35%SESSION 10
Integrating generative workflows with the Foundry SDKs and connectors
Session 10 of 40 · 45-minute session
Skills measured — Microsoft’s wording
  • Integrate generative workflows into applications by using Foundry SDKs and connectors

Why This Matters

A script that works on your laptop with four endpoints pasted into it is not an application. Production code finds its resources through configuration that someone else can govern, and authenticates without a single key. In Foundry, that is what project connections are for, alongside the two clients the Foundry SDK gives you and the connectors that reach business systems. AI-103 asks you to integrate generative workflows into applications with exactly these pieces, and to know which client does what. Today you connect your project to Azure AI Search, refactor your RAG script to find the service through that connection, and close Week 2.

IN YOUR SUBSCRIPTION
Creates
One project connection of type Azure AI Search on your Foundry project. If you deleted the search service in Session 7 (the recommended choice), you also recreate the Azure AI Search service (Microsoft.Search/searchServices, Basic) and its index, plus a chat and a text-embedding-3-small model deployment (Microsoft.CognitiveServices/accounts/deployments).
Cost
Billed. The search service is on the Dedicated pricing model: a fixed hourly rate for as long as it exists, used or not. Check the Azure AI Search pricing page for Basic. The model deployments are billed per token; see the Microsoft Foundry pricing page. Learn’s connections page describes no separate charge for the connection itself.
Clean-up
When your evidence is saved, delete the search service with az search service delete --name <your-search-name> --resource-group rg-ai103 --yes (Learn: this is the only way to stop Dedicated billing), and delete both deployments (Build → Models). The connection is only a record of the target and its authentication type; you can leave it on the Connected resources tab, knowing it points to nothing until you recreate a service. Keep rg-ai103 and the project for Week 3.
The 45-Minute Session
7:00 – 15:00
Project connections: where, not how much access
  • Learn defines them plainly: connections let you authenticate to Microsoft and other resources within your Foundry projects, and they are required for scenarios such as Standard agents and agent knowledge tools.
  • Connection type (as Learn lists it)What it reachesNote
    Azure AI SearchSearch indexes over vector and text dataRequired for Standard agent deployment
    Azure StorageUnstructured data such as documents and imagesRequired for Standard agent deployment
    Azure OpenAIOpenAI models with Azure security
    Application InsightsTelemetry for performance and diagnosisUsed for tracing (Session 17)
    Azure Key VaultSecrets, stored and accessed securely
    API key / Custom keyAny target that needs a key, or keys plus related propertiesKeys live in the connection, never in your code
    Grounding with Bing SearchReal-time web grounding for agents
  • A connection stores a target (the URL your code should use) and a credential type. The Python reference lists the credential types, including ApiKey and AAD, which is Microsoft Entra ID. With Entra ID the connection holds no secret at all: your code signs in as you, or as a managed identity, and the service’s own role assignments decide what you may do.
  • In code, project.connections.get(name) returns one connection and get_default(connection_type) returns the default connection of a type, such as ConnectionType.AZURE_AI_SEARCH. Both take include_credentials, which the reference says defaults to False. Leave it that way; a keyless app has nothing to include.
  • THE EXAM LINE

    A project connection supplies the target and authentication type for a connected resource. With Microsoft Entra ID authentication the app uses DefaultAzureCredential and role assignments, so no key is stored in code, configuration or the connection.

  • Who can add them: Learn’s page asks for Foundry User, Foundry Owner or Azure Contributor (or higher). Some types can only be created in code or Bicep; the page marks which.
A connection is a signpost, not a master key. Keep the permissions on the resource, where the security team can see them.
24:00 – 33:00
Refactor: from pasted endpoints to a connection
  • Your Session 7 script read SEARCH_ENDPOINT from an environment variable you typed by hand. The refactor asks the project instead, using the call shape from Learn’s Azure AI Search tool page, project.connections.get(...), and passes the connection’s target to SearchClient with the same credential as everything else.
  • pythonrag_connected.pytype it yourself
    import os
    from azure.identity import DefaultAzureCredential
    from azure.ai.projects import AIProjectClient
    from azure.ai.projects.models import CredentialType
    from azure.search.documents import SearchClient
    
    credential = DefaultAzureCredential()                 # one identity for everything
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential)
    
    # The connection says WHERE the search service is. Your identity decides WHETHER you may use it.
    search_conn = project.connections.get(os.environ["SEARCH_CONNECTION_NAME"])
    if search_conn.credentials.type == CredentialType.API_KEY:
        raise RuntimeError(f"{search_conn.name} uses a key; recreate it with Microsoft Entra ID")
    
    search = SearchClient(endpoint=search_conn.target,     # no SEARCH_ENDPOINT variable any more
                          index_name=os.environ["SEARCH_INDEX"], credential=credential)
    openai = project.get_openai_client()
    
    query = "How many days of compassionate leave can I take?"
    # Keyword query to keep this block short; keep your Session 7 hybrid query in your own refactor
    results = search.search(search_text=query, select=["id", "title", "content"], top=3)
    sources = "\n".join(f"[{d['id']}] {d['title']}: {d['content']}" for d in results)
    
    response = openai.responses.create(
        model=os.environ["FOUNDRY_MODEL_NAME"],
        instructions=("Answer ONLY with facts from the sources. Cite each fact with its [id]. "
                      "If the sources don't contain the answer, say you don't know."),
        input=f"Question: {query}\n\nSources:\n{sources}",
    )
    print(f"Search via connection '{search_conn.name}' ({search_conn.credentials.type})")
    print(response.output_text)
    WHAT YOU SHOULD SEE
    Search via connection '<your-connection-name>' (AAD)
    <a grounded answer with [id] citations, as in Session 7>
  • Notice what disappeared: the search endpoint variable. What remains in the environment is the project endpoint, the index name, the connection name and the deployment name, none of them secret. The guard refuses to run against a key-based connection, which is a cheap way to keep a policy honest.
  • When should you use get_default instead? When the project has one connection of that type marked default and your code should follow whatever the project owner points it at. A named connection is more explicit; a default is easier to swap. Both are keyless when the connection uses Entra ID.
  • Illustrative: a public agency runs the same app in a test project and a live project. Each project has its own Azure AI Search connection with the same name. The code does not change between them; only FOUNDRY_PROJECT_ENDPOINT does.
The best refactor removes a line of configuration. The second best adds a guard.
40:00 – 45:00
Wrap-up: Pop Quiz 2, the assignment and clean-up
  • Write down: the two Foundry SDK clients and what each is for; why embeddings use the OpenAI SDK endpoint; connections.get, get_default, ConnectionType, target and include_credentials; the Connected resources tab and Add connection; the Connector Namespace and project_connection_id.
  • This session closes Week 2 and opens Pop Quiz 2: Generative Applications. It covers Sessions 6 to 10: tuning, RAG, tool flows, evaluation and integration. Sit it before Session 11, closed book, with Coach Ritchie switched off.
  • Your assignment is a refactor of your Session 5 or Session 7 script to use project connections and keyless authentication, with evidence.
  • Clean-up, once your evidence is saved: delete the search service with az search service delete and delete both deployments. Keep rg-ai103 and the project; Week 3 builds agents on them.
  • If the connection will not add, or connections.get returns an error, send me the exact message and your role on the project through “Ask R. Wingo” today.
Week 2 done: you tuned, grounded, extended, measured and integrated. Next week the model starts making its own decisions, so these habits matter more, not less.

The exact names

As Microsoft prints them — the exam and the marker both expect these, letter for letter.
Connected resourcesportalThe Project details tab where you view and add a project’s connections.Learn →
Add connectionportalThe button on the Connected resources tab that starts a new project connection.Learn →
Project clientconceptThe Foundry SDK client for Foundry-native operations such as listing connections and enabling tracing.Learn →
OpenAI-compatible clientconceptThe client from get_openai_client() for Responses, agents, evaluations and fine-tuning.Learn →
connections.getmethodReturns one project connection by name, including its id and target.Learn →
SEARCH_CONNECTION_NAMEsettingThe name Learn’s Azure AI Search tool sample uses for the search connection’s name.Learn →
Custom keyconceptConnection type that stores keys and related properties securely for a custom target.Learn →
Foundry UserroleOne of the roles Learn lists as able to add project connections.Learn →
Foundry Tools CatalogportalWhere you browse over 1,000 pre-built connectors to SaaS, data and business systems.Learn →
Connector NamespaceserviceA managed service per Foundry account that hosts connector runtimes and managed MCP servers.Learn →
MCPToolclassPython class for an MCP server tool on an agent; takes project_connection_id for credentials.Learn →
project_connection_idparameterTool setting that references credentials stored in a project connection instead of embedding them.Learn →
Every name in the course is on one printable page: download the AI-103 cheat sheet (PDF).
Key Terms

Target

The URL a connection tells your code to use for the connected service.

Keyless integration

An integration where every call is authorised by a Microsoft Entra ID identity and role, with no key stored anywhere.

Self-Check — Before You Submit
1. A manufacturer runs one app in test and live Foundry projects, each pointing at a different search service. The team wants no endpoints or keys in the app’s configuration apart from the project endpoint. What should they do?
  • A. Paste each search service’s admin key into an environment variable
  • B. Hard-code both search endpoints and choose one with an if statement
  • C. Store the search key in the system message
  • D. Add an Azure AI Search connection with Microsoft Entra ID to each project, and read its target with connections.get
D. Add an Azure AI Search connection with Microsoft Entra ID to each project, and read its target with connections.get — A project connection supplies the target per project, and Entra ID authentication means no key is stored anywhere. Environment variables holding admin keys still put secrets in configuration, which the scenario rules out.
2. Which client should your code use to list a Foundry project’s connections?
  • A. The project client from AIProjectClient
  • B. The OpenAI-compatible client from get_openai_client()
  • C. The OpenAI SDK on the /openai/v1 endpoint
  • D. SearchClient from azure-search-documents
A. The project client from AIProjectClient — Learn’s SDK overview says the project client handles Foundry-native operations such as listing connections. The OpenAI-compatible client is for Responses, agents, evaluations and fine-tuning, which have OpenAI equivalents.
3. An agent must call a GitHub MCP server that needs a token. How does Learn say the token should be supplied?
  • A. In the agent’s instructions
  • B. Stored in a project connection and referenced with project_connection_id in the tool definition
  • C. As a query string on the server URL
  • D. In a code comment next to the tool
B. Stored in a project connection and referenced with project_connection_id in the tool definition — Learn’s tool catalog page says to store the credential in a project connection and reference that connection when you create the tool. Putting a token in instructions exposes it to the model and to anyone who reads the agent.

Today’s Assignment

Refactor your Session 5 or Session 7 script so that it finds its resources through project connections and authenticates without any key, and show the evidence.
  • Choose your Session 7 RAG script (recommended) or your Session 5 two-model script. List every endpoint, name and credential it used before the refactor.
  • Add at least one project connection with Microsoft Entra ID authentication on the Connected resources tab (Azure AI Search for Session 7). Run list_connections.py and keep the output.
  • Refactor the script so it reads the connected resource’s target with project.connections.get (or get_default, and say why), uses DefaultAzureCredential for every client, and refuses a key-based connection. Keep your hybrid query if you refactor Session 7.
  • Write 300–700 words: a before-and-after table of configuration values and where each now comes from; which calls use the project client and which the OpenAI-compatible client; why no key is needed; and one connector from the Foundry Tools Catalog your organisation could use, with how it would authenticate.
  • Include your full code. Never include keys, secrets or credentials, and crop endpoints and connection targets out of screenshots. Delete the search service and deployments afterwards, and say so.
Evidence to include
  • A screenshot of the Connected resources tab showing your connection and its authentication type, with the target cropped.
  • The output of list_connections.py showing the connection type and an auth type that is not ApiKey (targets may be masked).
  • The output of the refactored script showing the connection name it used and a working answer, and the output of az search service delete or a screenshot of the resource group afterwards.
Names your answer must use exactly
Connected resources Project client OpenAI-compatible client connections.get Foundry Tools Catalog project_connection_id
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 refactored script runs, reads its resource target from a project connection, uses DefaultAzureCredential throughout, and refuses a key-based connection35
The before-and-after table and explanation correctly assign each call to the right client and explain the keyless design and the connector choice20
Uses the exact portal, client, method and parameter names from this session’s names box20
Evidence: the connection screenshot, connection listing and script output are included, with no keys, endpoints or targets visible, and clean-up is shown25
Total · pass mark 70100
Submitting session 10
Write your answer in Word or Google Docs, with your screenshots pasted in. The top of the document must carry the line Session: 10 (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 2 — Generative Applications 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 6–10 · pass mark 70
Sit Pop Quiz 2 →Closed book, no notes, and Coach Ritchie switches himself off while you sit it.
Skills Covered
Foundry SDK clientsProject connectionsKeyless integrationConnectors and managed MCP serversRefactoring to connections
Tick it once you have submitted.Next: Session 11 →
← Back to course home