The Microsoft Learn examples for Foundry exist in several languages, and the exam page names Python as the field of experience. This course writes .NET (the stack of Nordlicht Reisen GmbH and our own); there is no Python code of our own here. Python still has to be read: Anyone looking for an example of a new feature often finds it there first, and anyone taking the exam should be able to read it. This lesson shows how to read a Python example, which line corresponds to which in C# (according to lessons 2.1 to 2.4) and which two quirks confuse.
Prior knowledge. The C# side of the patterns is in lessons 2.1 (project client, responses), 2.3 (agent version, conversation) and 2.4 (tools); the IChatClient is taught by "AI track, lesson T2.1". This lesson does not repeat them, it matches them up.
About the snippets. Python appears here only as reading samples: short snippets in our own words and with our own names that show the pattern of the Learn examples. They are not taken from Microsoft and were not run. The names of the calls come from the Learn examples, the names of the variables are ours.
The requirement
All numbers are assumptions of the task.
| No. | Requirement | Type | Measure | Weight | Source |
|---|---|---|---|---|---|
| 0.3-01 | A Python example from Learn of at most 15 lines is explained in your own words. | F | 3 parts named: imports, connection, call | Must | Exam list |
| 0.3-02 | For every pattern of the course the C# line is known. | F | 10 matches | Must | Lessons 2.1 to 2.4 |
| 0.3-03 | Asynchronous and dictionary notation are recognized. | Q | 2 of 2 quirks named | Should | Readability |
| 0.3-04 | Your own code stays .NET. | R | 0 new files in Python | Must | Stack of the course |
How to read an example
A Learn example almost always has three parts. Imports name the packages: azure.identity (sign-in) and azure.ai.projects (Foundry project). Connection: a client built from the project endpoint and a credential. Call: a method on the client or on a client derived from it. When reading, the order of the questions helps: Which client is it? Where does the name of the model or agent come from, from a configuration or as text in the example? What comes back, and which field is read?
projekt = AIProjectClient(endpoint=ENDPOINT, credential=DefaultAzureCredential())
openai = projekt.get_openai_client()
antwort = openai.responses.create(model="chat", input="Hello")
print(antwort.output_text)
Reading sample 1. Connection, a client for model calls, a call, a field. In the Learn example a model name stands as text; in the course the name of the task from the configuration stands there (R-08). The exam page does not say how its tasks show code: open.
definition = PromptAgentDefinition(model="chat", instructions="You help with travel.")
agent = projekt.agents.create_version(
agent_name="reiseassistent", definition=definition
)
openai = projekt.get_openai_client(agent_name=agent.name)
gespraech = openai.conversations.create()
Reading sample 2. An agent is created as a version; a second client is bound to the agent; a conversation is created on the client. After that openai.responses.create(conversation=gespraech.id, input="…") is called, as in lesson 2.3.
Python and C# side by side
The right column is the code from the named lessons (Azure.AI.Projects 2.0.1, Azure.AI.Projects.Agents 2.0.0, Azure.AI.Extensions.OpenAI 2.0.0 and Azure.Identity 1.21.0, unless the lesson says otherwise).
| Pattern | Python in Learn examples | C# in this course | Lesson |
|---|---|---|---|
| Project client | AIProjectClient(endpoint=…, credential=DefaultAzureCredential()) |
new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential()) |
2.1 |
| Endpoint from environment | os.environ["NAME"] |
Environment.GetEnvironmentVariable("NAME") ?? throw … |
2.1 |
| Client for a model | projekt.get_openai_client(), model=… in the call |
ProjectOpenAIClient.GetProjectResponsesClientForModel("chat") |
2.1 |
| Call, text field | openai.responses.create(input=…), .output_text |
CreateResponseAsync("…"), GetOutputText() |
2.1 |
| Agent definition | PromptAgentDefinition(model=…, instructions=…) |
new DeclarativeAgentDefinition("chat") { Instructions = … } |
2.3 |
| Create a version | projekt.agents.create_version(agent_name=…, definition=…) |
AgentAdministrationClient.CreateAgentVersionAsync(name, options: new(definition)) |
2.3 |
| Client on the agent | projekt.get_openai_client(agent_name=…) |
GetProjectResponsesClientForAgent(defaultAgent: name, …) |
2.3 |
| Conversation | openai.conversations.create() |
GetProjectConversationsClient().CreateProjectConversationAsync() |
2.3 |
| Delete a version | projekt.agents.delete_version(agent_name=…, agent_version=…) |
DeleteAgentVersionAsync(name, version) |
2.3 |
| Tools | tools=[…] in the definition |
Tools = { … } (collection initializer) |
2.3, 2.4 |
| Read output items | for item in response.output: and item.type == "function_call" |
foreach (ResponseItem item in response.OutputItems) and item is FunctionCallResponseItem |
2.4 |
| Cleanup in a block | with … as x: |
using |
all |
In addition the notations: snake_case becomes PascalCase, named arguments (name=value) become named arguments or object initializers, None becomes null, dictionaries and lists become types and collection expressions, f"…{x}" becomes $"…{x}". Not covered in lessons 2.1 to 2.4 is the stream of events that Python examples read with stream=True; for streamed responses the course uses the IChatClient of the AI track (lesson T2.2). Whether there is an equivalent way for the events of the responses client in .NET is open.
Two quirks that confuse
Asynchronous. A function with async def does not yet return a result when called, but a pending object; only await fetches it. In addition there are async with (open and close a client) and async for. In C# this is async Task and await, and the compiler warns when you forget to wait; in Python you notice only later. The Learn pages that were read show the synchronous form for azure-ai-projects; whether and how an asynchronous version of the package is used is open.
async def hole(client, text):
async with client as sitzung:
return await sitzung.lade(text) # made-up call, only the pattern
Reading sample 3. The pattern of the asynchronous Azure SDKs, without a real service.
Dictionary options. Where C# demands a type, in Python a dictionary is often enough: input=[{"role": "user", "content": "…"}]. For fields that the client does not know there is extra_body. Earlier Learn examples specified the agent this way; newer ones bind it to the client (get_openai_client(agent_name=…)). Anyone reading older examples sees both.
ref = {"agent_reference": {"name": "reiseassistent", "type": "agent_reference"}}
antwort = openai.responses.create(
input=[{"role": "user", "content": "Hello"}],
extra_body=ref,
)
Reading sample 4. Two dictionaries: the conversation as a list and the reference to the agent. A typo in a key is noticed only at run time; in C# it is a type (AgentReference, lesson 2.3).
Decision: read, carry over or replace
- If the page has a C# tab, it applies: It comes from Microsoft and fits the package. Its version is often stated; it does not always match the series that this course uses (lesson 2.1: 2.x stable, preview only where Learn demands it).
- If it has none, the Python example is carried over with the table and the C# version is compiled (exercise 1).
- If the pattern is missing from the table too, the REST interface of the page counts, if there is one.
Flips when type names of the preview differ (lesson 2.1 names examples): Then the name of the installed package version applies, not that of the table.
State of the checks
The C# block of the solution was compiled on .NET 10 with Azure.AI.Projects 2.0.1, Azure.AI.Projects.Agents 2.0.0, Azure.AI.Extensions.OpenAI 2.0.0 and Azure.Identity 1.21.0. Compiling proves only names and signatures. The code was not run against Azure. The Python snippets (reading samples) were neither run nor compiled; they show patterns, not checked programs. The matching in the table rests on the Learn examples and on the code of lessons 2.1 to 2.4. Open: events in a stream in .NET, asynchronous form of the Python package.
Exercise 1
Carry this reading sample over to C# (a file-based app) and compile it:
definition = PromptAgentDefinition(model="chat", instructions="Check the draft.")
agent = projekt.agents.create_version(agent_name="pruefer", definition=definition)
openai = projekt.get_openai_client(agent_name=agent.name)
print(openai.responses.create(input="Draft: …").output_text)
projekt.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
Solution 1
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using OpenAI.Responses;
#pragma warning disable OPENAI001
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is missing.");
AIProjectClient projekt = new(new Uri(endpoint), new DefaultAzureCredential());
DeclarativeAgentDefinition definition = new("chat")
{
Instructions = "Check the draft.",
};
ProjectsAgentVersion agent = await projekt.AgentAdministrationClient
.CreateAgentVersionAsync("pruefer", options: new(definition));
ProjectResponsesClient openai = projekt.ProjectOpenAIClient
.GetProjectResponsesClientForAgent(defaultAgent: agent.Name);
ResponseResult antwort = await openai.CreateResponseAsync("Draft: …");
Console.WriteLine(antwort.GetOutputText());
await projekt.AgentAdministrationClient
.DeleteAgentVersionAsync(agent.Name, agent.Version);
Five lines of Python become a small program: The types have to be named, the calls wait with await, the endpoint comes from the environment. The name chat is here, as in the reading sample, a text; in the application it would come from the configuration (R-08).
Exercise 2
Read without running and answer: (a) Which line waits for the network? (b) What does a call of frage(...) return if you forget to wait? (c) What does an element of the list input look like in C#?
async def frage(client, text):
async with client:
nachricht = [{"role": "user", "content": text}]
antwort = await client.responses.create(input=nachricht)
return antwort.output_text
Solution 2
(a) The line with await. (b) A pending object instead of a text; only await frage(...) (or starting the event loop) returns the text. In C# frage would be an async Task<string>, and the forgotten await would be caught by the compiler. (c) ResponseItem.CreateUserMessageItem(text), as in lesson 2.4; the list there is a List<ResponseItem>.
Typical mistakes
- "The example is Python, so it is not for us." The patterns are the same; the table translates them.
- "The model name in the example is our deployment." It appears as text in the example; with us the deployment is named after the task, and the model is in the configuration.
- "
extra_bodyis a core feature." It is a way out for fields that the client does not know; newer examples bind the agent to the client. - "The exam requires writing Python." The page names experience with Python; how it shows code is not stated there. You have to practice it yourself.
Further reading
- https://learn.microsoft.com/azure/foundry/quickstarts/get-started-code
- https://learn.microsoft.com/azure/foundry/agents/quickstarts/prompt-agent
- https://learn.microsoft.com/azure/foundry/how-to/develop/sdk-overview
- https://learn.microsoft.com/azure/foundry/agents/concepts/runtime-components
- https://learn.microsoft.com/credentials/certifications/resources/study-guides/ai-103