Azure AI-901 · Module 3 · Session 6

Session 6: Chat App with the Foundry SDK

⏱ 10 min read📘 Live-session notes☁️ Microsoft Foundry

Connect Python to your Foundry project with keys or keyless sign-in, build a lightweight chat client with the Foundry SDK, keep conversation history across turns, handle errors, rate limits and token usage, and optionally put a Streamlit front end on it.

The pieces

Piece Value Where to find it
Project endpoint https://<resource>.services.ai.azure.com/api/projects/<project> Project Overview page
Deployment name the name you gave the deployed model, for example chat-model Models → Deployments
Azure OpenAI v1 endpoint (key option) https://<resource>.openai.azure.com/openai/v1/ Project Overview / resource keys page
API key (key option) a secret string Project Overview, or the resource’s Keys and Endpoint page

Install the packages (Python 3.10+):

pip install "azure-ai-projects>=2.3.0" azure-identity openai python-dotenv

The Foundry SDK (azure-ai-projects) uses Microsoft Entra ID through DefaultAzureCredential. Run az login first, and make sure your account has a role such as Azure AI User on the project. The project client gives you a ready OpenAI client:

import os
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient

load_dotenv()

project = AIProjectClient(
    endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
    credential=DefaultAzureCredential(),
)
client = project.get_openai_client()          # OpenAI-compatible client for this project

response = client.responses.create(
    model=os.environ["FOUNDRY_MODEL_NAME"],      # the DEPLOYMENT name
    instructions="You are a helpful assistant for Contoso Bank. Keep answers short.",
    input="What is a fixed deposit?",
)
print(response.output_text)

DefaultAzureCredential tries several sign-in methods in order: environment variables, a managed identity (when running in Azure), the Azure CLI login, VS Code and others. The same code works on your laptop and in production.

Option 2: API key

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(
    base_url=os.environ["FOUNDRY_OPENAI_ENDPOINT"],   # https://<resource>.openai.azure.com/openai/v1/
    api_key=os.environ["FOUNDRY_API_KEY"],
)
response = client.responses.create(model=os.environ["FOUNDRY_MODEL_NAME"], input="Hello!")
print(response.output_text)

Keys are quick for labs, but store them only in .env or Azure Key Vault, never in code or Git, and rotate them if they leak.

Responses API vs Chat Completions

Foundry supports both OpenAI-style APIs. The Responses API (client.responses.create) is the newer one: it takes instructions and input, returns output_text, can keep state with previous_response_id, and supports tools. The older Chat Completions API (client.chat.completions.create(messages=[...])) returns choices[0].message.content. Know both shapes for the exam.

Managing conversation history

A model is stateless: it remembers nothing between calls unless you send the history.

Approach A: send the full message list yourself

history = [{"role": "system", "content": "You are Asha, Contoso Bank's FAQ assistant. Answer in under 80 words."}]

while True:
    user = input("You: ").strip()
    if user.lower() in {"quit", "exit"}:
        break
    history.append({"role": "user", "content": user})
    response = client.responses.create(model=os.environ["FOUNDRY_MODEL_NAME"], input=history)
    answer = response.output_text
    history.append({"role": "assistant", "content": answer})
    print("Asha:", answer)

Approach B: let the service chain turns

first = client.responses.create(model=MODEL, input="What is the size of France in square miles?")
follow = client.responses.create(model=MODEL, input="And what is the capital city?",
                                 previous_response_id=first.id)     # the service keeps the context
print(follow.output_text)

Long conversations grow the token count (and cost) on every turn. Keep the last N turns, or summarise older turns into one message, so you stay inside the context window.

Errors, rate limits and token usage

Each deployment has a tokens-per-minute (TPM) and requests-per-minute (RPM) quota. Exceeding it returns HTTP 429 (Too Many Requests). Retry with exponential backoff:

import time
from openai import RateLimitError, APIConnectionError, APIStatusError

def ask(messages, retries=4):
    for attempt in range(retries):
        try:
            r = client.responses.create(model=MODEL, input=messages, max_output_tokens=400)
            u = r.usage
            print(f"[tokens] in={u.input_tokens} out={u.output_tokens} total={u.total_tokens}")
            return r.output_text
        except RateLimitError:
            wait = 2 ** attempt
            print(f"Rate limited, retrying in {wait}s...")
            time.sleep(wait)
        except APIConnectionError:
            print("Network problem, retrying...")
            time.sleep(2)
        except APIStatusError as e:          # 400 (bad request / content filter), 401, 404...
            print("Request failed:", e.status_code, e.message)
            return None
    return None

Common errors:

Optional: a Streamlit front end

# app.py  ->  run with:  streamlit run app.py
import os, streamlit as st
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient

load_dotenv()
MODEL = os.environ["FOUNDRY_MODEL_NAME"]

@st.cache_resource
def get_client():
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
    return project.get_openai_client()

st.title("🏦 Contoso Bank Assistant")
if "history" not in st.session_state:
    st.session_state.history = [{"role": "system", "content": "You are a concise, polite bank FAQ assistant."}]

for m in st.session_state.history[1:]:
    st.chat_message(m["role"]).write(m["content"])

if prompt := st.chat_input("Ask about accounts, cards or UPI"):
    st.session_state.history.append({"role": "user", "content": prompt})
    st.chat_message("user").write(prompt)
    answer = get_client().responses.create(model=MODEL, input=st.session_state.history).output_text
    st.session_state.history.append({"role": "assistant", "content": answer})
    st.chat_message("assistant").write(answer)

Check yourself

Which Python class from the Foundry SDK connects to your project, and which method returns an OpenAI client? AIProjectClient, and get_openai_client().
Your app gets HTTP 429 errors at peak time. What should the code do? Retry with exponential backoff and, if needed, increase the deployment’s tokens-per-minute quota or spread the load.
Why does the model “forget” the user’s name between two separate calls? Models are stateless. You must send the conversation history (or use previous_response_id) for context.
Which credential class lets the same code work locally (Azure CLI login) and in Azure (managed identity)? DefaultAzureCredential.
Created by Ajeetkumar