
An agent is only as useful as what it can reach. Give it the wrong tool and it guesses. Give it the wrong knowledge source and it cites the wrong policy. Forget memory and it asks a returning customer the same question twice. AI-103 tests this as a design choice: which built-in tool, which custom tool, which knowledge service, which kind of memory. This is the agents week, and it starts on paper on purpose. Today you learn the current tool catalogue in Foundry Agent Service, what each tool is for and whether it is generally available, and you design the tools, knowledge and memory for an agent you would actually build.
az cognitiveservices account deployment delete. Keep rg-ai103 and the project.In the current Foundry Agent Service, threads became conversations, runs became responses, and assistants became versioned agents. Conversations store items (messages, tool calls, tool outputs), not only messages.
| Tool (Learn’s name) | What it does | New Foundry status, at the time of writing |
|---|---|---|
| Web search | Real-time public web results with inline citations | GA |
| File Search | Searches files you upload into a vector store | GA |
| Code Interpreter | Writes and runs Python in a sandbox for analysis, charts and files | GA |
| Azure AI Search | Grounds answers in an existing Azure AI Search index | GA |
| Function calling | The model asks for your function; your app runs it and returns the result | GA |
| OpenAPI tool | Calls an external HTTP API described by an OpenAPI 3.0 or 3.1 specification | GA |
| Model Context Protocol (MCP) | Calls tools hosted on a remote MCP server endpoint | GA |
| Agent-to-Agent (A2A), SharePoint, Microsoft Fabric, Browser Automation, Computer Use, Image Generation | Specialist tools for other agents, Microsoft 365 and Fabric data, browsers, desktops and images | Preview |
Web search and Grounding with Bing Search both send data outside the Azure compliance boundary, and the Microsoft Data Protection Addendum does not apply to data sent to Grounding with Bing. Exclude them when data must stay inside.
| Knowledge option | Where the content lives | Choose it when |
|---|---|---|
| File Search | Files uploaded into a vector store: Microsoft-managed storage and search in Basic agent setup, your connected Blob Storage and Azure AI Search in Standard setup | A modest set of documents, uploaded by you or the user, with no index to maintain |
| Azure AI Search tool | An index you already build and own | The index exists (as in Session 7) and you want its query types; the tool targets one index |
| Foundry IQ knowledge base | One or more knowledge sources, such as Azure Blob Storage, SharePoint, OneLake or public web data | Several sources, shared by several agents, with permission-aware answers and citations through agentic retrieval |
| Web search | The public web | The answer changes daily and public sources are acceptable |
File Search for uploaded files in a vector store. Azure AI Search tool for an existing index, one index per tool. Foundry IQ for multi-source, permission-aware knowledge shared by agents. Web search for current public information.
previous_response_id. Long-term memory retains distilled knowledge across sessions, so a returning user does not start from nothing.| Memory type | What it keeps | Example (illustrative) |
|---|---|---|
| User profile memory | Durable preferences and personal context, such as language or accessibility needs | A patient prefers letters in large print |
| Chat summary memory | Distilled summaries of earlier conversation topics | Last week the visitor asked about parking at the east entrance |
| Procedural memory | Reusable routines inferred from earlier interactions | This clerk always exports the ward list before filtering it |
scope partitions the memories, typically one scope per end user.Conversation history is short-term memory for one session. A memory store (preview) is long-term memory across sessions, with user profile, chat summary and procedural memory, partitioned by scope and usually attached through the memory search tool.
Web search | tool | Built-in tool that grounds answers in real-time public web results with inline citations. | Learn → |
File Search | tool | Built-in tool that searches files you upload into a vector store. | Learn → |
Code Interpreter | tool | Built-in tool that writes and runs Python code in a sandboxed environment. | Learn → |
Function calling | tool | Custom functions the agent can ask for; your app runs them and returns the result. | Learn → |
OpenAPI tool | tool | Connects an agent to an external HTTP API described by an OpenAPI 3.0 or 3.1 specification. | Learn → |
Model Context Protocol (MCP) | tool | Connects an agent to tools hosted on a remote MCP server endpoint. | Learn → |
Toolbox | feature | A curated, versioned set of tools exposed through one MCP-compatible endpoint for many agents. | Learn → |
Grounding with Bing Search | tool | Web grounding tool that needs your own Grounding with Bing Search resource and a project connection. | Learn → |
memory store | concept | The managed store (preview) that holds an agent’s long-term memories across sessions. | Learn → |
User profile memory | concept | Memory type for durable preferences and personal context, such as language or accessibility needs. | Learn → |
Chat summary memory | concept | Memory type holding distilled summaries of earlier conversation topics. | Learn → |
Procedural memory | concept | Memory type holding reusable routines inferred from earlier interactions. | Learn → |
knowledge sources | concept | The connections to indexed or remote content that make up a Foundry IQ knowledge base. | Learn → |
A preconfigured capability you switch on for an agent; the service runs it, with no hosting or code of your own.
A tool you supply: your own function, an OpenAPI-described API, an MCP server or another agent.
The object File Search uses: files added to it are parsed, chunked, embedded and stored for search.
The key that partitions a memory store, usually one per end user, so one person’s memories never reach another.
Web search File Search Function calling Model Context Protocol (MCP) memory store Toolbox| Each tool, knowledge and memory choice fits the agent and rules out a plausible alternative | 35 |
| Status, data-boundary and authentication decisions are stated and justified | 20 |
| Uses the exact tool and memory names from this session’s names box | 20 |
| Evidence: the tables, the status screenshots and the diagram are included, with no keys or secrets anywhere | 25 |
| Total · pass mark 70 | 100 |
Session: 11 (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.The objective reads “Define agent roles, goals, conversation-tracking approach, and tool schemas”, and each of those four nouns is a place where real agents go wrong. Vague instructions give you an agent that wanders. A loose tool schema gives you arguments your code cannot use. The wrong conversation-tracking choice gives you an agent that forgets what it asked a minute ago, or one that keeps data you were told not to keep. Today you write an agent’s role and goals properly, define a function tool with a strict JSON schema, choose how the conversation is tracked, and run it in Python with a trace of every item.
parcel-agent) with one or more immutable versions, and one or more conversations, inside your Foundry project. It calls a model deployment in rg-ai103: if you deleted yours at the end of Week 2, redeploy one on Global Standard as in Session 4. The function itself runs on your laptop, not in Azure.cleanup.py in the wrap-up to delete every agent version and the conversation. Keep the model deployment for Session 13; if you are pausing for more than a day, delete it (Foundry portal → Build → Models, or az cognitiveservices account deployment delete).tool_choice enforces. Learn lists three values: auto (the model decides whether to call tools), required (it must call one or more tools) and none (it calls no tools). Use required on a turn that must never be answered from the model’s own knowledge.Put role, goals, tool-use rules and limits in the instructions. For the most deterministic control, set tool_choice: auto lets the model decide, required forces at least one tool call, none prevents tool calls.
FunctionTool. The parameters object has type, properties (a type and description for each), required, and additionalProperties set to False so the model cannot invent fields. Learn’s function-calling sample also sets strict=True, which enforces stricter validation of the schema.FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL_NAME, the deployment name), then create the agent. The code follows Learn’s Use function calling with Microsoft Foundry agents how-to.import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import PromptAgentDefinition, FunctionTool
INSTRUCTIONS = """Role: You are the parcel-tracking assistant for a Lagos logistics firm.
Goals: 1) Tell customers where their parcel is. 2) Set honest expectations.
3) Hand over to a human colleague when you are unsure.
Tools: Call get_parcel_status whenever the customer gives a tracking ID.
If the ID is missing, ask for it. Never guess a status.
Limits: Never promise refunds or delivery dates that the tool did not return.
Style: Plain English, three sentences or fewer."""
status_tool = FunctionTool(
name="get_parcel_status",
description="Look up the latest status of a parcel by its tracking ID.",
parameters={
"type": "object",
"properties": {
"tracking_id": {"type": "string", "description": "Tracking ID, for example LOS-48213"},
"detail": {"type": "string", "enum": ["summary", "full"], "description": "How much history to return"},
},
"required": ["tracking_id", "detail"],
"additionalProperties": False,
},
strict=True,
)
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
agent = project.agents.create_version(
agent_name="parcel-agent",
definition=PromptAgentDefinition(
model=os.environ["FOUNDRY_MODEL_NAME"], # your deployment name
instructions=INSTRUCTIONS,
tools=[status_tool],
),
)
print(f"Agent created (name: {agent.name}, version: {agent.version})")Agent created (name: parcel-agent, version: 1)enum where only a few values make sense, as with detail; Learn’s model function-calling page uses the same keyword for temperature units. And write every description for a new colleague: an example value such as LOS-48213 teaches the format better than a paragraph.FunctionTool takes a name, a description and a parameters JSON schema (type, properties, required, additionalProperties); strict=True enforces stricter validation. The model only proposes the call. Your application runs the function.
| Approach | How it works | Choose it when |
|---|---|---|
| Conversation object | openai.conversations.create(), then pass conversation= on every response. The service stores the items and appends each response to them | Multi-turn chats, returning users, or you want to inspect tool calls afterwards |
| Response chaining | Pass previous_response_id. By default the service stores response history server-side, so the next response picks up the context | A short exchange where you do not need a durable conversation object |
| Client-side state | Set store=False. Nothing is persisted, so your app passes the earlier turns back as input itself | Policy forbids storing prompts and outputs in the service |
conversations.items.create().Conversation object for durable multi-turn history; previous_response_id to chain responses; store=False when nothing may be persisted, so the app carries context. Conversations replaced threads in the current Foundry Agent Service.
function_call item with a call_id and JSON arguments. Your app runs the function and sends a function_call_output with the same call_id, in the same conversation. The agent then writes the final answer.agent_reference in extra_body, as Learn’s samples do. Session 4 used the other documented pattern, project.get_openai_client(agent_name=...), which returns a client already bound to one agent. Both reach the same agent; here the client stays unbound and each request names the agent. The script also prints the type of every output item. That printout is the trace your assignment needs.import json
import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from openai.types.responses.response_input_param import FunctionCallOutput
def get_parcel_status(tracking_id: str, detail: str) -> str:
# Illustrative stand-in for the firm's tracking system
return json.dumps({"tracking_id": tracking_id, "status": "At Abuja hub", "updated": "09:40"})
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
openai = project.get_openai_client()
agent = {"agent_reference": {"name": "parcel-agent", "type": "agent_reference"}}
conversation = openai.conversations.create() # durable, server-side history
print("Conversation:", conversation.id)
for text in ["Where is my parcel?", "Sorry, the tracking ID is LOS-48213."]:
print("User:", text)
response = openai.responses.create(conversation=conversation.id, input=text, extra_body=agent)
outputs = []
for item in response.output:
print(f" [{item.type}]")
if item.type == "function_call" and item.name == "get_parcel_status":
print(f" -> {item.name}({item.arguments})")
outputs.append(FunctionCallOutput(
type="function_call_output",
call_id=item.call_id,
output=get_parcel_status(**json.loads(item.arguments)),
))
if outputs: # your app ran the function: hand the result back in the same conversation
response = openai.responses.create(conversation=conversation.id, input=outputs, extra_body=agent)
print("Agent:", response.output_text)Conversation: <conversation-id>
User: Where is my parcel?
[message]
Agent: <asks you for the tracking ID>
User: Sorry, the tracking ID is LOS-48213.
[function_call]
-> get_parcel_status({"tracking_id":"LOS-48213","detail":"summary"})
Agent: <says the parcel is at the Abuja hub, updated 09:40>INSTRUCTIONS, run define_agent.py again and note the new version number. The endpoint serves the latest version by default, so the next conversation uses it.tool_choice values; the JSON schema keys and what strict does; the three conversation-tracking approaches; the function_call to function_call_output hand-back by call_id.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()
# Use the values your scripts printed. Repeat delete_version for every version you made.
project.agents.delete_version(agent_name="parcel-agent", agent_version="<version>")
openai.conversations.delete(conversation_id="<conversation-id>")az login and check you hold Foundry User on the project. If it still fails, send me the exact error the same day.FunctionTool | class | The Foundry SDK class that defines a function tool: name, description, parameters schema, strict. | Learn → |
strict | parameter | FunctionTool setting that enforces stricter validation of the parameters schema. | Learn → |
additionalProperties | parameter | JSON schema key; set to false so the model cannot send fields you did not define. | Learn → |
function_call_output | parameter | The input item type your app sends back with a function’s result. | Learn → |
call_id | parameter | Links a function_call_output to the function_call item it answers. | Learn → |
conversations.delete | method | Deletes a stored conversation and the items it holds. | Learn → |
tool_choice | parameter | Controls tool calling on a response: auto, required or none. | Learn → |
agent_reference | parameter | The extra_body entry that tells a responses call which agent to run. | Learn → |
conversations.items.create | method | Adds items, such as a user message, to an existing conversation. | Learn → |
Message items | concept | Conversation items holding user or assistant messages. | Learn → |
Tool call items | concept | Conversation items recording the tool invocations the agent attempted. | Learn → |
Conversations | concept | The current replacement for classic threads; they hold streams of items, not only messages. | Learn → |
The standing briefing of a prompt agent: its role, goals, tool-use rules, limits and style.
A JSON description of allowed data: its fields, their types, which are required and which values are permitted.
One stored record in a conversation, such as a message, a tool call or a tool output.
An output item in which the model asks your app to run a named function with given arguments.
FunctionTool: a clear name, a description with an example value, a parameters schema with at least two properties (one of them an enum), required, additionalProperties set to false, and strict=True. Implement the function locally with illustrative data.track_conversation.py does.previous_response_id or store=False) against a data policy you state, and say where tool_choice would help.FunctionTool strict additionalProperties function_call_output call_id tool_choice agent_reference| The agent and function tool work: the schema is strict and complete, and the conversation shows a correct function_call and function_call_output hand-back | 35 |
| The instructions, limits and conversation-tracking choice are well reasoned against a stated policy | 20 |
| Uses the exact class, parameter and method names from this session’s names box | 20 |
| Evidence: the traced output, both versions and the clean-up are shown, with no keys, endpoints or secrets visible | 25 |
| Total · pass mark 70 | 100 |
Session: 12 (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.Session 12 gave an agent one action. A useful agent needs three things at once: knowledge it can cite, actions it can take, and a memory of what the user has already said. That combination is what the objective “Build agents that integrate retrieval, function-calling, and conversation memory” tests, and it is the shape of most agents you will build at work. Today you build one end to end in Python: your own document behind File Search, a function tool, and a multi-turn conversation that carries context between turns. You will also see how long-term memory differs, so you can explain exactly which memory you used and why.
hr-leave-agent) with File Search and a function tool, and one conversation, all in your project. In Basic agent setup the file and vector store use Microsoft-managed storage and search; in Standard setup they use your connected Blob Storage and Azure AI Search. The optional memory step adds a memory store and a second agent, and needs a chat model and an embedding model deployment.cleanup.py and delete_file.sh in the wrap-up: they delete the agent version, the conversation, the vector store and the uploaded file. If you did the memory step, uncomment its lines to delete that agent and the memory store, then delete the embedding deployment. Keep the chat model deployment for Session 14.An agent that retrieves and acts: PromptAgentDefinition with tools=[FileSearchTool(vector_store_ids=[...]), FunctionTool(...)], run inside one conversation so earlier answers and tool results are available on later turns.
leave_policy.md, with five or six invented rules, such as a carry-over limit, a deadline for using carried-over days and a notice period. Mark it illustrative. Never upload real personnel data for an exercise.vector_stores.create makes the store and the upload_and_poll helper adds the file and returns only when ingestion has finished. Learn is clear that files must be fully processed before you rely on them.import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool, FunctionTool
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
openai = project.get_openai_client()
# Knowledge: a vector store holding your own (illustrative) policy file
vector_store = openai.vector_stores.create(name="leave-policy-store")
with open("leave_policy.md", "rb") as file_handle:
vs_file = openai.vector_stores.files.upload_and_poll(vector_store_id=vector_store.id, file=file_handle)
# Action: a function your app runs
balance_tool = FunctionTool(
name="get_leave_balance",
description="Return the remaining annual leave days for a staff ID such as E1042.",
parameters={
"type": "object",
"properties": {"staff_id": {"type": "string", "description": "Staff ID, for example E1042"}},
"required": ["staff_id"],
"additionalProperties": False,
},
strict=True,
)
agent = project.agents.create_version(
agent_name="hr-leave-agent",
definition=PromptAgentDefinition(
model=os.environ["FOUNDRY_MODEL_NAME"],
instructions="You answer staff questions about annual leave. Use File Search for any policy "
"rule and say which rule you used. Use get_leave_balance only when the user gives "
"a staff ID. If the policy does not cover the question, say you don't know.",
tools=[FileSearchTool(vector_store_ids=[vector_store.id]), balance_tool],
),
)
print(f"Agent {agent.name} v{agent.version} | vector store {vector_store.id} | file {vs_file.id}")Agent hr-leave-agent v1 | vector store <vector-store-id> | file <file-id>vector_stores.create creates the store; upload_and_poll adds a file and waits for processing; FileSearchTool(vector_store_ids=[...]) attaches it to the agent. At most one vector store per agent and one per conversation.
agent_reference, as in Session 12. When a response contains function_call items, the app runs the function, sends each result back as a function_call_output with its call_id, and repeats until the agent answers in words. The printed item types are your trace.import json
import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from openai.types.responses.response_input_param import FunctionCallOutput
BALANCES = {"E1042": 12, "E2077": 3} # illustrative stand-in for the HR system
def get_leave_balance(staff_id: str) -> str:
return json.dumps({"staff_id": staff_id, "days_left": BALANCES.get(staff_id)})
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
openai = project.get_openai_client()
ref = {"agent_reference": {"name": "hr-leave-agent", "type": "agent_reference"}}
conversation = openai.conversations.create() # carries memory between the turns below
def ask(text: str) -> None:
print("User:", text)
response = openai.responses.create(conversation=conversation.id, input=text, extra_body=ref)
while True:
print(" trace:", [item.type for item in response.output])
calls = [i for i in response.output if i.type == "function_call" and i.name == "get_leave_balance"]
if not calls:
break
outputs = [FunctionCallOutput(type="function_call_output", call_id=c.call_id,
output=get_leave_balance(**json.loads(c.arguments))) for c in calls]
response = openai.responses.create(conversation=conversation.id, input=outputs, extra_body=ref)
print("Agent:", response.output_text)
ask("How many days of annual leave can I carry over into next year?")
ask("My staff ID is E1042. How many days do I have left?")
ask("If I carry over the maximum, how many of those days must I take this year?")
print("Conversation:", conversation.id)User: How many days of annual leave can I carry over into next year?
trace: ['file_search_call', 'message']
Agent: <the carry-over rule from your policy file, with the rule named>
User: My staff ID is E1042. How many days do I have left?
trace: ['function_call']
trace: ['message']
Agent: <12 days left>
User: If I carry over the maximum, how many of those days must I take this year?
trace: [<item types, often file_search_call and message>]
Agent: <an answer combining the balance and the rule from earlier turns>
Conversation: <conversation-id>file_search_call in the trace, the agent answered from general knowledge. Tighten the instructions and save a new version, or send that turn with tool_choice="required".AzureAISearchTool wraps an AzureAISearchToolResource, whose AISearchIndexResource takes the project connection ID, the index name and a query type from AzureAISearchQueryType. The supported values are simple, vector, semantic, vector_simple_hybrid and vector_semantic_hybrid, which is the default.project.connections.get(...). For keyless access, Learn’s prerequisites name the system-assigned managed identity of the Foundry resource that contains your project; it needs Search Index Data Contributor and Search Service Contributor on the search service, and Learn says key-based authentication is not supported with private virtual networking.import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
MemorySearchPreviewTool,
MemoryStoreDefaultDefinition,
MemoryStoreDefaultOptions,
PromptAgentDefinition,
)
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
chat_model = os.environ["MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME"]
store = project.beta.memory_stores.create(
name="hr_memory_store",
definition=MemoryStoreDefaultDefinition(
chat_model=chat_model,
embedding_model=os.environ["MEMORY_STORE_EMBEDDING_MODEL_DEPLOYMENT_NAME"],
options=MemoryStoreDefaultOptions(
chat_summary_enabled=True,
user_profile_enabled=True,
user_profile_details="Avoid sensitive data, such as age, financials, precise location, and credentials",
),
),
description="Long-term memory for the HR leave agent",
)
agent = project.agents.create_version(
agent_name="hr-memory-agent",
definition=PromptAgentDefinition(
model=chat_model,
instructions="You help staff with leave questions and remember the preferences they state.",
# "{{$userId}}" isolates memories per end user; update_delay=1 is for the demo only (default 300)
tools=[MemorySearchPreviewTool(memory_store_name=store.name, scope="{{$userId}}", update_delay=1)],
),
)
print(f"Memory store {store.name} | agent {agent.name} v{agent.version}")Memory store hr_memory_store | agent hr-memory-agent v1update_delay; the demo uses 1 second, and Learn’s default is 300 seconds, which is what production should use.Conversation: short-term memory within one chat. Memory store (preview) with MemorySearchPreviewTool: long-term memory across chats. scope "{{$userId}}" isolates each user; update_delay debounces writes, default 300 seconds.
vector_stores.create, upload_and_poll and vector_store_ids; the Azure AI Search tool classes and query types; short-term versus long-term memory; MemorySearchPreviewTool, scope and update_delay.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()
# Values printed by build_agent.py and chat.py
project.agents.delete_version(agent_name="hr-leave-agent", agent_version="<version>")
openai.conversations.delete(conversation_id="<conversation-id>")
openai.vector_stores.delete("<vector-store-id>")
# Only if you ran memory_agent.py
# project.agents.delete_version(agent_name="hr-memory-agent", agent_version="<version>")
# project.beta.memory_stores.delete("hr_memory_store")# Learn's file-search clean-up deletes the uploaded file separately from the vector store
AGENT_TOKEN=$(az account get-access-token --scope https://ai.azure.com/.default --query accessToken -o tsv)
curl --request DELETE \
--url $FOUNDRY_PROJECT_ENDPOINT/openai/v1/files/<file-id-printed-by-build_agent> \
-H "Authorization: Bearer $AGENT_TOKEN"upload_and_poll fails, check the file type against Learn’s list and your role on the project. If the error persists, send me the exact message the same day.FileSearchTool | class | Attaches File Search to an agent, pointing it at a vector store. | Learn → |
vector_store_ids | parameter | The FileSearchTool parameter listing the vector store the agent searches. | Learn → |
vector_stores.create | method | Creates a vector store through the project’s OpenAI client. | Learn → |
upload_and_poll | method | Uploads a file into a vector store and returns once processing has finished. | Learn → |
vector_stores.delete | method | Deletes a vector store when you no longer need it. | Learn → |
AzureAISearchTool | class | Attaches an existing Azure AI Search index to an agent through a project connection. | Learn → |
AzureAISearchQueryType | class | Chooses the query type: simple, vector, semantic or one of the two hybrids. | Learn → |
Search Index Data Contributor | role | Role the project’s managed identity needs on the search service for keyless use of the tool. | Learn → |
MemorySearchPreviewTool | class | Attaches a memory store to a prompt agent so it reads and writes long-term memory. | Learn → |
MemoryStoreDefaultDefinition | class | Defines a memory store: its chat model, embedding model and options. | Learn → |
update_delay | parameter | Seconds of inactivity before memories are written; the default is 300. | Learn → |
Short-term memory | concept | Context for the current session, such as a conversation’s items. | Learn → |
Long-term memory | concept | Distilled knowledge an agent keeps across sessions in a memory store. | Learn → |
A slice of a document, measured in tokens, that is embedded and retrieved on its own.
A named, stored link from a Foundry project to another resource, such as a search service, used instead of keys in code.
The partition of a memory store that keeps one user’s memories apart from another’s.
Waiting for a quiet period before acting, so several quick changes lead to one write.
build_agent.py, or use the Azure AI Search tool on your Session 7 index if you still have it.chat.py does.memory_agent.py, show and explain its result.FileSearchTool vector_store_ids upload_and_poll Short-term memory Long-term memory MemorySearchPreviewTool| The agent works: retrieval and the function tool are both used correctly across a multi-turn conversation | 35 |
| The memory explanation is correct, including the new-conversation test and a justified view on long-term memory | 20 |
| Uses the exact class, method, parameter and concept names from this session’s names box | 20 |
| Evidence: the traces, the new-conversation output and the clean-up output are shown, with no keys or endpoints visible | 25 |
| Total · pass mark 70 | 100 |
Session: 13 (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.An agent earns its keep when it reaches real systems: a partner’s API, your own search index, a document pipeline, a function in your codebase. AI-103 names all of these in one objective, and its questions are rarely about syntax. They ask which integration fits, how it authenticates, and who approves a call before it runs. Today you connect two different tool types to one agent: an OpenAPI tool built from a specification, and an MCP server that needs a person’s approval for every call. You also place knowledge stores, search and Content Understanding correctly in a design, so you can defend every tool you choose.
tools-agent) in your project, and the stored responses from your test calls. No project connection is needed: the OpenAPI sample uses anonymous authentication, and the Microsoft Learn MCP Server needs none. Nothing else is created in Azure. A Foundry IQ knowledge base is discussed but not built today.cleanup_tools.py in the wrap-up to delete every tools-agent version. If you created a project connection or a search service for anything optional, delete the search service from Resource groups → rg-ai103, and the connection with the Azure Resource Manager DELETE call Learn shows on its Foundry IQ page. Keep the model deployment for Session 15.| What you integrate | Tool in Foundry Agent Service | Authentication, as Learn documents it |
|---|---|---|
| An HTTP API (partner or internal) | OpenAPI tool, from an OpenAPI 3.0 or 3.1 specification | Anonymous; API key held in a project connection; or managed identity for Microsoft Entra ID-protected APIs |
| Tools published by an MCP server | MCP tool (remote endpoints only) | Key-based, Microsoft Entra identities or OAuth identity passthrough, stored in a project connection; none for public servers |
| Your own code | Function calling: your app runs it | Whatever your app uses; only the result goes back to the agent |
| Search over an index you own | Azure AI Search tool (one index per tool) | Project connection: key-based, or keyless with the project’s managed identity |
| A knowledge store shared by agents | Foundry IQ knowledge base, reached through the MCP tool | Project connection of category RemoteTool using the project’s managed identity |
OpenAPI tool: an OpenAPI 3.0 or 3.1 spec with an operationId for each operation. Authentication is anonymous, an API key through a project connection, or managed identity for Microsoft Entra ID-protected services.
OpenApiTool wrapping an OpenApiFunctionDefinition (name, spec, description, auth). Learn’s first-success path uses OpenApiAnonymousAuthDetails with a public weather API, so you prove the mechanics before you handle credentials.operationId. Learn requires one for every operation, using only letters, - and _, and descriptive names help the model choose the right one. Request bodies must be application/json or application/json-patch+json.MCPTool, has server_label (your name for the server), server_url, require_approval, optionally allowed_tools to limit which of the server’s tools the agent may use, and project_connection_id when the server needs authentication.require_approval defaults to always. It also accepts never, or an object listing tools under never or always. With approval on, the response contains an mcp_approval_request item, and your app answers with an McpApprovalResponse carrying approve and the approval_request_id, chained with previous_response_id.MCPTool: server_label, server_url, require_approval (default always), allowed_tools, project_connection_id. An mcp_approval_request item is answered with an McpApprovalResponse (approve, approval_request_id) sent with previous_response_id.
allowed_tools allow-list, approval for high-risk operations, and a log of approvals and tool calls.# In your course virtual environment (Python 3.10+)
pip install "azure-ai-projects>=2.4.0" azure-identity jsonref # 2.4.0: the floor since Session 9
# Learn's first-success flow: save the sample spec into an assets folder
mkdir -p assets
# Download weather_openapi.json from the link on Learn's OpenAPI how-to into ./assets/
az login
export FOUNDRY_PROJECT_ENDPOINT="https://<resource-name>.services.ai.azure.com/api/projects/<project-name>"
export FOUNDRY_MODEL_NAME="<your-deployment-name>"https://learn.microsoft.com/api/mcp. Learn states that it needs no authentication and that there is no charge to use it, which makes it a safe first server. Define both tools in one module; the OpenAPI part follows Learn’s Connect OpenAPI tools to Microsoft Foundry agents how-to.import os
import jsonref
from azure.ai.projects.models import (
MCPTool,
OpenApiAnonymousAuthDetails,
OpenApiFunctionDefinition,
OpenApiTool,
)
# Tool type 1: an HTTP API described by an OpenAPI spec, anonymous auth (Learn's first-success flow)
with open(os.path.join("assets", "weather_openapi.json"), "r") as f:
openapi_weather = jsonref.loads(f.read())
weather_tool = OpenApiTool(
openapi=OpenApiFunctionDefinition(
name="get_weather",
spec=openapi_weather,
description="Retrieve weather information for a location.",
auth=OpenApiAnonymousAuthDetails(),
)
)
# Tool type 2: a remote MCP server. The Microsoft Learn MCP Server needs no authentication,
# so no project_connection_id is set. A person must approve every call.
learn_tool = MCPTool(
server_label="mslearn",
server_url="https://learn.microsoft.com/api/mcp",
require_approval="always",
)agent_reference in extra_body. The approval loop follows Learn’s Connect agents to Model Context Protocol servers how-to: print what the server wants to do, let a person decide, send the decision back, and repeat until no approval is pending.import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import PromptAgentDefinition
from openai.types.responses.response_input_param import McpApprovalResponse
from tools_def import learn_tool, weather_tool
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
openai = project.get_openai_client()
agent = project.agents.create_version(
agent_name="tools-agent",
definition=PromptAgentDefinition(
model=os.environ["FOUNDRY_MODEL_NAME"],
instructions="Use get_weather for weather questions. Use the mslearn tools for questions "
"about Microsoft products. Always say which tool you used.",
tools=[weather_tool, learn_tool],
),
)
ref = {"agent_reference": {"name": agent.name, "type": "agent_reference"}}
print(f"Agent {agent.name} v{agent.version}")
for question in ["What is the weather in Lagos today?", "Which Azure CLI command group manages Foundry resources?"]:
print("User:", question)
response = openai.responses.create(input=question, extra_body=ref)
while True:
approvals = []
for item in response.output:
print(f" [{item.type}]")
if item.type == "mcp_approval_request":
print(f" {item.server_label} wants {item.name} with {item.arguments}")
ok = input(" Approve? y/N: ").strip().lower() == "y"
approvals.append(McpApprovalResponse(
type="mcp_approval_response", approve=ok, approval_request_id=item.id))
if not approvals:
break
response = openai.responses.create(input=approvals, previous_response_id=response.id, extra_body=ref)
print("Agent:", response.output_text)Agent tools-agent v1
User: What is the weather in Lagos today?
[<the OpenAPI tool call item>]
[message]
Agent: <current weather for Lagos, saying it used get_weather>
User: Which Azure CLI command group manages Foundry resources?
[<other items may appear first>]
[mcp_approval_request]
mslearn wants <tool name> with <arguments>
Approve? y/N: y
[<the MCP call item>]
[message]
Agent: <an answer drawn from Microsoft Learn, saying it used the mslearn tools>operationId. If the MCP call fails, check that you left project_connection_id out for this unauthenticated server and that your network allows the endpoint.RemoteTool, authenticated with ProjectManagedIdentity, pointing at the knowledge base’s MCP endpoint on the search service; then an MCPTool with require_approval set to never and allowed_tools set to knowledge_base_retrieve. The project’s managed identity needs Search Index Data Reader on the search service. You build agentic retrieval in Session 27.A Foundry IQ knowledge base connects to an agent through MCPTool with allowed_tools set to knowledge_base_retrieve, using a RemoteTool project connection that authenticates with the project’s managed identity.
operationId rule; the MCPTool settings and the approval round trip; how a Foundry IQ knowledge base is attached; where Content Understanding fits.import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
# One call per version that tools_agent.py printed
for version in ["1"]:
project.agents.delete_version(agent_name="tools-agent", agent_version=version)
print("tools-agent versions deleted")OpenApiTool | class | Adds an OpenAPI-described HTTP API to an agent as a tool. | Learn → |
OpenApiFunctionDefinition | class | Holds an OpenAPI tool’s name, specification, description and authentication. | Learn → |
OpenApiAnonymousAuthDetails | class | Authentication setting for public APIs that need no credential. | Learn → |
operationId | parameter | Required name for each operation in the spec; letters, hyphens and underscores only. | Learn → |
MCPTool | class | Connects an agent to a remote MCP server endpoint. | Learn → |
server_label | parameter | Your identifying label for an MCP server in the tool definition. | Learn → |
require_approval | parameter | Whether MCP tool calls need approval: always (default), never, or per tool. | Learn → |
mcp_approval_request | concept | The output item type raised when an MCP call awaits approval. | Learn → |
McpApprovalResponse | class | Your app’s answer to an approval request: approve and approval_request_id. | Learn → |
allowed_tools | parameter | An allow-list of the MCP server’s tools the agent may call. | Learn → |
knowledge_base_retrieve | tool | The knowledge base MCP tool an agent is allowed to call to query Foundry IQ. | Learn → |
RemoteTool | setting | The project connection category used to reach a knowledge base’s MCP endpoint. | Learn → |
Azure Content Understanding skill | feature | Azure AI Search skill that uses Content Understanding analyzers to extract and chunk content at indexing time. | Learn → |
A machine-readable description of an HTTP API: its operations, parameters, request bodies and security schemes.
A service that publishes tools through the Model Context Protocol so that agents can discover and call them.
A pause in which a person, or your app’s policy, allows or refuses a specific tool call before it runs.
Instructions hidden in content a tool returns, written to make the model do something its user did not ask for.
tools_def.py and tools_agent.py. If you use a spec of your own, check every operationId against Learn’s rule. If you use an MCP server other than Microsoft Learn’s, set allowed_tools and keep approval on.OpenApiTool OpenApiAnonymousAuthDetails operationId MCPTool require_approval mcp_approval_request McpApprovalResponse| The agent works with at least two tool types, and the approval round trip is implemented correctly | 35 |
| The explanation of fit, authentication, data flow and the place of knowledge stores or Content Understanding is correct | 20 |
| Uses the exact class, parameter and item names from this session’s names box | 20 |
| Evidence: tool-call traces, the approved and declined requests, and clean-up are shown, with no keys or secrets visible | 25 |
| Total · pass mark 70 | 100 |
Session: 14 (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.Some jobs are too big, or too risky, for one agent with one prompt. A complaint needs drafting and reviewing; a payment decision needs rules that no model is allowed to bend. AI-103 asks you to orchestrate multiple agents and to combine models with flows and rules engines, and this is where the platform has moved fastest. Connected Agents is not in the new Agent Service, and portal workflows are being retired. Today you learn what Learn currently offers, build a two-agent sequential orchestration with Microsoft Agent Framework, and put deterministic rules before and after a model, with a trace you could show an auditor.
az cognitiveservices account deployment delete) and check Build → Agents for versions left from Sessions 12 to 14. Keep rg-ai103 and the project.| Option (current Learn name) | What it is | Status at the time of writing |
|---|---|---|
| Microsoft Agent Framework workflows | Code-first or declarative YAML orchestration of agents and functions; run in Foundry by deploying as a hosted agent | Learn’s recommended path for new workflows |
| Foundry workflows (portal) | A visual builder with Human in the loop, Sequential and Group chat patterns, Power Fx expressions and a YAML view | Preview; Microsoft Foundry is retiring workflows on December 1, 2026 |
| A2A tool | One agent calls another agent’s A2A-compatible endpoint and keeps control of the conversation | Preview |
| Azure Logic Apps | A low-code designer that can call Foundry agents as steps alongside deterministic actions | Learn’s migration path when a visual designer is the requirement |
| Connected Agents | The classic Agents API tool for calling one agent from another | Not available in the new Foundry Agent Service; Learn recommends the A2A tool |
Connected Agents is classic only. In the new Foundry Agent Service, connect agents with the A2A tool or orchestrate them with workflows. For new workflows Learn recommends Microsoft Agent Framework, because portal workflows retire on December 1, 2026.
Sequential: one after another. Concurrent: in parallel. Handoff: agents transfer control by context. Group chat: a shared conversation. Magentic: a manager agent coordinates specialists. Human in the loop: pause for a person.
FoundryChatClient uses a model deployed in your Foundry project, while your app owns the agent definitions and the loop.# In your course virtual environment
pip install agent-framework azure-identity
az login # AzureCliCredential, used below, reads this sign-in
# Already set since Week 1 (the second name is the course's own)
echo $FOUNDRY_PROJECT_ENDPOINT
echo $FOUNDRY_MODEL_NAMEawait; here they sit inside asyncio.run, as Learn’s own Foundry samples do. AzureCliCredential reads your az login session.import asyncio
import os
from agent_framework.foundry import FoundryChatClient
from agent_framework.orchestrations import SequentialBuilder
from azure.identity import AzureCliCredential
async def main() -> None:
chat_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL_NAME"], # Learn's sample calls this FOUNDRY_MODEL
credential=AzureCliCredential(),
)
drafter = chat_client.as_agent(
name="drafter",
instructions="You draft a short, polite reply to a customer complaint. Three sentences at most.",
)
reviewer = chat_client.as_agent(
name="reviewer",
instructions=(
"You review the previous reply. List any promise of money or dates, and any tone problem. "
"Then give a corrected reply."
),
)
# Sequential orchestration: drafter -> reviewer
workflow = SequentialBuilder(participants=[drafter, reviewer]).build()
events = await workflow.run("My parcel from Lagos to Abuja is five days late and nobody answers the phone.")
outputs = events.get_outputs()
if outputs:
for msg in outputs[0].messages: # the whole hand-over, author by author
print(f"[{msg.author_name or 'assistant'}]\n{msg.text}\n")
if __name__ == "__main__":
asyncio.run(main())<the original request may be listed first>
[drafter]
<a three-sentence apology and next step>
[reviewer]
<issues found, for example an unapproved delivery date, then a corrected reply>Executor: a code class that receives the conversation and yields output, listed among the participants beside the agents.responses.create, from Session 5). Illustrative: a Lagos bank’s complaint router.import json
import os
import re
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
ALLOWED = {"BILLING", "CARD", "TRANSFER", "OTHER"} # rule: the only routes the bank accepts
ACCOUNT_NO = re.compile(r"\b\d{10}\b") # rule: 10-digit account numbers never reach the model
PROMPT = ('Classify this complaint. Reply with JSON only, for example '
'{"route": "BILLING", "reason": "short reason"}. Routes: BILLING, CARD, TRANSFER, OTHER.')
project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
openai = project.get_openai_client()
def route(ticket: str) -> dict:
trace = []
if "fraud" in ticket.lower(): # rule before: hard stop
trace.append("pre-rule: fraud keyword -> FRAUD_TEAM, model not called")
return {"route": "FRAUD_TEAM", "trace": trace}
clean, count = ACCOUNT_NO.subn("[ACCOUNT]", ticket) # rule before: redact
trace.append(f"pre-rule: redacted {count} account number(s)")
r = openai.responses.create(model=os.environ["FOUNDRY_MODEL_NAME"], input=f"{PROMPT}\n\n{clean}")
trace.append(f"model: {r.output_text}")
try: # rule after: validate
result = json.loads(r.output_text)
except json.JSONDecodeError:
result = {}
if not isinstance(result, dict) or result.get("route") not in ALLOWED:
trace.append("post-rule: output not allowed -> HUMAN_QUEUE")
return {"route": "HUMAN_QUEUE", "trace": trace}
trace.append("post-rule: accepted")
return {"route": result["route"], "trace": trace}
for ticket in ["I think this is fraud: a card payment I never made.",
"Transfer from 0123456789 failed but the money left my account."]:
print(json.dumps(route(ticket), indent=2)){
"route": "FRAUD_TEAM",
"trace": [
"pre-rule: fraud keyword -> FRAUD_TEAM, model not called"
]
}
{
"route": "TRANSFER",
"trace": [
"pre-rule: redacted 1 account number(s)",
"model: {\"route\": \"TRANSFER\", \"reason\": \"<short reason>\"}",
"post-rule: accepted"
]
}Hybrid orchestration: deterministic rules before the model (block, redact, route) and after it (validate against an allow-list or schema, fall back to a person). A model’s free text should never trigger a consequential action unchecked.
SequentialBuilder and FoundryChatClient; rules before and after the model.A2APreviewTool(project_connection_id=...) to the calling agent. When the target is another Foundry agent, enable incoming A2A on it and give the calling identity the Foundry Agent Consumer role or higher on the target project.Connected Agents | tool | Classic agent-to-agent tool; not available in the new Foundry Agent Service, where Learn recommends the A2A tool. | Learn → |
A2APreviewTool | class | Adds an Agent2Agent (A2A) endpoint, through a project connection, as a tool for an agent. | Learn → |
Foundry Agent Consumer | role | Role the calling identity needs, at least, on a target Foundry project for A2A calls. | Learn → |
Microsoft Agent Framework | tool | Microsoft’s SDK for building agents and multi-agent workflows; Learn’s recommended path for new workflows. | Learn → |
Human in the loop | feature | Workflow pattern that asks the user a question and waits for input, such as an approval. | Learn → |
Group chat | concept | Workflow pattern that passes control between agents based on context or rules. | Learn → |
Power Fx | feature | The Excel-like, low-code formula language used for expressions and conditions in Foundry workflows. | Learn → |
Azure Logic Apps | service | Low-code workflow service that can call Foundry agents as steps alongside deterministic actions. | Learn → |
agent-framework | package | The Python package for Microsoft Agent Framework. | Learn → |
FoundryChatClient | class | Agent Framework client that uses a model deployed in a Foundry project from your own code. | Learn → |
SequentialBuilder | class | Builds a pipeline in which agents process the task in turn. | Learn → |
Executor | class | Agent Framework base class for a custom code step inside a workflow. | Learn → |
Handoff | concept | Orchestration pattern in which agents transfer control to each other based on context. | Learn → |
Magentic | concept | Orchestration pattern in which a manager agent dynamically coordinates specialised agents. | Learn → |
Deciding which agent or step acts, in what order, and who chooses the next step.
Deterministic logic that gives the same result for the same input every time, used to block, route or validate.
A standard protocol that lets one agent call another agent’s endpoint and exchange context.
The safe path taken when a step fails or its output is not allowed, such as a queue for a person.
sequential.py into a two-agent (or larger) sequential orchestration with SequentialBuilder and FoundryChatClient. Option B: adapt rules_router.py with at least one rule before the model and one after it, and a fallback. Strong submissions do both.SequentialBuilder FoundryChatClient Microsoft Agent Framework Connected Agents A2APreviewTool Handoff| The orchestration runs on three inputs, and the pattern or rules are implemented correctly with a fallback | 35 |
| The explanation of pattern choice, rule placement and the current Foundry options is correct and current | 20 |
| Uses the exact class, package, service and pattern names from this session’s names box | 20 |
| Evidence: traces for every run, a rule or review in action, and the clean-up, with no keys or endpoints visible | 25 |
| Total · pass mark 70 | 100 |
Session: 15 (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.Pop Quiz 3 — Agents 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.
Sit Pop Quiz 3 →Closed book, no notes, and Coach Ritchie switches himself off while you sit it.