AI-103 courseLesson 6 of 18 · 14 min
Build Generative AI Apps With the Foundry SDK in Python (AI-103)
XUtilHub Team ·
Exam skills covered
- Deploy and consume LLMs, small models, code models, and multimodal models
- Integrate generative workflows into applications by using Foundry SDKs and connectors
- Configure an application to connect to a Foundry project
This lesson starts the biggest domain on AI-103, generative AI and agentic solutions (30–35%). You'll connect a Python app to a Foundry project and call different kinds of models with the Responses API: plain chat, streaming, structured JSON and images.
Connect an app to a Foundry project
Install the SDKs:
pip install azure-ai-projects azure-identity openai
Read configuration from the environment, not from code:
import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
project = AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], # https://<resource>.services.ai.azure.com/api/projects/<project>
credential=DefaultAzureCredential(),
)
openai = project.get_openai_client()
MODEL = os.environ.get("CHAT_DEPLOYMENT", "chat-main")
What's happening:
AIProjectClientconnects to the project endpoint.DefaultAzureCredentialis keyless: your Azure CLI login locally, a managed identity in Azure. The identity needs the Azure AI User role on the project.get_openai_client()returns an OpenAI-compatible client already pointed at the project.MODELis your deployment name, not necessarily the model's name.
The project client also gives you connections (project.connections.list()), deployments, datasets, indexes, evaluations and agents, so one client covers most of the Foundry SDK.
Call a model
response = openai.responses.create(
model=MODEL,
instructions="You are a concise assistant for Contoso's HR team.",
input="Summarise our parental leave policy in three bullet points.",
max_output_tokens=400,
)
print(response.output_text)
instructionsis the system message.inputis a string, or a list of messages and content parts.output_textjoins the text output.response.usagereports input and output tokens for cost tracking.
Multi-turn: pass previous_response_id=response.id on the next call, or create a server-side conversation with openai.conversations.create() and pass conversation=conv.id.
Small, code and reasoning models
The same call works for any chat-capable deployment. Only model changes:
cheap = openai.responses.create(model="phi-mini", input="Classify: 'My card was charged twice' → billing/tech/sales")
code = openai.responses.create(model="codex-mini", input="Write a Python function that validates an IBAN.")
hard = openai.responses.create(
model="reasoning-main",
input="Plan a 3-phase migration of 40 VMs with these constraints: ...",
reasoning={"effort": "high"},
)
Some models in the catalog aren't OpenAI models. Many of them are served through the same Foundry endpoint, so you still call them by deployment name. Reasoning models accept reasoning effort instead of (or as well as) temperature. Check each model's supported parameters.
Stream responses
Streaming sends tokens as they're generated, which cuts time to first token, the latency users actually notice:
stream = openai.responses.create(model=MODEL, input="Explain RAG to a new developer.", stream=True)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
Structured outputs
When code consumes the answer, ask for JSON that matches a schema:
schema = {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "technical", "sales"]},
"urgency": {"type": "integer", "minimum": 1, "maximum": 5},
"summary": {"type": "string"},
},
"required": ["category", "urgency", "summary"],
"additionalProperties": False,
}
r = openai.responses.create(
model=MODEL,
input="Ticket: 'Production API is down since 9am, we're losing orders!'",
text={"format": {"type": "json_schema", "name": "ticket", "schema": schema, "strict": True}},
)
ticket = json.loads(r.output_text)
strict: True constrains generation to the schema, which is far more reliable than "please reply in JSON".
Multimodal input
Send an image next to text using content parts:
r = openai.responses.create(
model=MODEL,
input=[{
"role": "user",
"content": [
{"type": "input_text", "text": "What error is shown in this screenshot?"},
{"type": "input_image", "image_url": "data:image/png;base64," + b64_png},
],
}],
)
Need a quick base64 string to test with? Use our image to Base64 converter.
Integrating into applications
- Keep the AI call behind your own API layer, so the browser never talks to the model directly
- Add timeouts, retries and fallbacks (for example a second deployment) for 429s and outages
- Log usage per feature and user for cost analytics
- Connectors such as Logic Apps, Power Automate and Microsoft 365 connectors let workflows call your models and agents without custom code
Key takeaways
AIProjectClient(endpoint, DefaultAzureCredential())→get_openai_client()→responses.create(model=<deployment>, ...).- Swap models by changing the deployment name; reasoning models use reasoning effort.
- Stream for responsiveness; use structured outputs with a JSON schema for machine-readable answers.
- Images go in as
input_imagecontent parts.
Next lesson: implement RAG with Azure AI Search.
Check your understanding
Exam-style questions on this lesson. Get them all right to mark the lesson complete.
Finished reading?