AI Tutorial
Home All Courses Blog Interview Prep ↗ Export PDF 0%
1
Curious builder 0 XP earned · 300 to level 2
0 days Finish a lesson to begin
Badge collection
0 of 5 unlocked
small wins to finish your path
Next lesson →

Calling models from code

Export PDF

Foundry speaks the OpenAI API shape, so almost everything you know about calling OpenAI applies. This lesson shows the two ways to get a client (through the Foundry SDK, or directly with the OpenAI SDK), explains the Responses and Chat Completions APIs, and covers multi-turn chat, streaming, structured output, embeddings, the errors you will see, and a retry strategy that respects rate limits.

Last updated: 01 Nov, 2026 · azure-ai-projects 2.x · Python 3.10+

Which SDK should you use?

SDKPackageUse it when
Foundry SDKazure-ai-projectsYou use agents, evaluations, datasets, connections, or tracing. It also hands you a ready OpenAI client.
OpenAI SDKopenaiYou only need model inference, want the lowest latency, need embeddings, or are porting existing OpenAI code.
Anthropic SDKanthropicYou deployed Claude models in Foundry.
Agent Frameworkagent-frameworkYou orchestrate multi-agent systems in code (covered briefly in the Agent Service lesson).

Most apps use the Foundry SDK and call get_openai_client() on it. You get one authenticated client for both worlds.

Option 1: through the Foundry SDK

Live Azure call

This example calls your Azure resources, so it needs a deployed project and a signed-in identity. Output will differ on every run.

Python
import os

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

load_dotenv()  # reads FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL_DEPLOYMENT from .env

project = AIProjectClient(
    endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
    credential=DefaultAzureCredential(),
)

with project.get_openai_client() as client:
    response = client.responses.create(
        model=os.environ["FOUNDRY_MODEL_DEPLOYMENT"],   # deployment name, e.g. "gpt-5-mini"
        instructions="You are a concise assistant for Azure engineers.",
        input="In one sentence, what is a Foundry project?",
    )

print(response.output_text)
print(response.usage.input_tokens, "in /", response.usage.output_tokens, "out")
A Foundry project is a workspace inside a Foundry resource where you build and run agents, store data, and evaluate AI apps using shared model deployments. 31 in / 34 out

get_openai_client() returns a standard openai.OpenAI client whose base URL is your project endpoint plus /openai/v1, and whose “API key” is a function that fetches fresh Entra ID tokens (scope https://ai.azure.com/.default). You never handle a token yourself.

Option 2: the OpenAI SDK directly

Python
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_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1",
    api_key=token_provider,          # a callable: refreshed automatically, no api-version needed
)

response = client.responses.create(
    model=os.environ["FOUNDRY_MODEL_DEPLOYMENT"],
    input="Give me three uses of Azure AI Search.",
)
print(response.output_text)
The v1 API

Older Azure OpenAI code used the AzureOpenAI client with an api_version="2024-xx-xx" argument. The current /openai/v1 route needs no API version and works with the plain OpenAI client — the same code runs against OpenAI and Foundry by changing only base_url and the credential.

Responses API vs Chat Completions

Definition · Responses API

The newer OpenAI-style API (client.responses.create). Takes input (a string or a list of items) and instructions, returns a list of output items (messages, tool calls, reasoning), supports server-side state through previous_response_id or conversations, and built-in tools. Foundry agents are built on it.

Definition · Chat Completions API

The classic API (client.chat.completions.create) that takes a messages list and returns choices. Stateless, widely supported, and still the way to call some partner models.

ResponsesChat Completions
Inputinput + instructionsmessages list with roles
Outputresponse.output_text, response.output itemschoices[0].message.content
StateOptional server-side (previous_response_id, conversation)You resend full history every time
ToolsFunction tools plus built-in tools (file search, code interpreter, MCP, ...)Function tools
Use forNew code and anything agent-likeLegacy code and some partner models
Python
# the same question with Chat Completions, on the account-level OpenAI endpoint
account_client = OpenAI(
    base_url=f"https://{resource_name}.openai.azure.com/openai/v1",
    api_key=token_provider,
)
completion = account_client.chat.completions.create(
    model=os.environ["FOUNDRY_MODEL_DEPLOYMENT"],
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "What is a deployment in Foundry?"},
    ],
)
print(completion.choices[0].message.content)
print(completion.choices[0].finish_reason)   # "stop", "length", "content_filter", "tool_calls"

Multi-turn conversations

With Responses you can let the service keep the history. Pass the previous response's ID and only send the new turn:

Python
first = client.responses.create(
    model=deployment,
    input="My name is Priya and I manage the support team.",
)
second = client.responses.create(
    model=deployment,
    previous_response_id=first.id,          # the service replays the earlier turn
    input="What is my name and role?",
)
print(second.output_text)   # "You are Priya, and you manage the support team."

For longer-lived chats, create a conversation object (client.conversations.create()) and pass conversation=conv.id on every call. The agent lessons use this pattern. Remember: stored history still counts as input tokens on every turn.

Streaming

Streaming sends tokens as they are generated, so users see text in under a second instead of waiting for the full reply.

Python
stream = client.responses.create(
    model=deployment,
    input="Explain Global Standard vs Data Zone deployments in 4 bullet points.",
    stream=True,
)
for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
    elif event.type == "response.completed":
        usage = event.response.usage
        print(f"\n[{usage.input_tokens} in / {usage.output_tokens} out]")

Structured output

When code consumes the answer, ask for JSON that matches a schema. The model is constrained to produce valid output.

Python
from pydantic import BaseModel


class Ticket(BaseModel):
    category: str        # "billing" | "shipping" | "technical" | "other"
    urgency: int         # 1 (low) .. 3 (high)
    summary: str


parsed = client.responses.parse(
    model=deployment,
    instructions="Classify the customer message into a support ticket.",
    input="My card was charged twice for order A1002 and I need it fixed today!",
    text_format=Ticket,
)
ticket: Ticket = parsed.output_parsed
print(ticket.model_dump())
{'category': 'billing', 'urgency': 3, 'summary': 'Customer was double-charged for order A1002 and wants an urgent fix.'}

Embeddings

Embeddings turn text into vectors for search and clustering. Deploy an embedding model (for example text-embedding-3-large) and call it through the account-level OpenAI endpoint:

Python
embed_client = OpenAI(
    base_url=f"https://{resource_name}.openai.azure.com/openai/v1",
    api_key=token_provider,
)
result = embed_client.embeddings.create(
    model="text-embedding-3-large",      # your embedding deployment name
    input=["refund policy for damaged items", "how do I return a product?"],
)
print(len(result.data), "vectors of", len(result.data[0].embedding), "dimensions")

Errors you will see

Status / errorMeaningFix
401 UnauthorizedNo valid token or keyRun az login; check the token scope and endpoint
403 ForbiddenAuthenticated but missing a role, or blocked by network rulesAssign Foundry User; check firewall or private endpoint
404 DeploymentNotFoundThe model value is not a deployment on this resourceUse the deployment name, not the catalog name; check the endpoint
400 content_filterThe prompt was blocked by a guardrailHandle gracefully; see the guardrails lesson
429 Too Many RequestsTPM or RPM exceededRetry with backoff using retry-after; raise quota; reduce max_output_tokens
500 / 503Transient service issueRetry with backoff; consider a fallback deployment in another region

Retries that respect rate limits

The OpenAI SDK retries 429 and 5xx errors twice by default (configure with max_retries). For high-traffic services, understand the logic yourself: honour the service's retry-after header when present, otherwise back off exponentially with jitter (randomness) so many clients do not retry in lockstep.

Python
import random


class RateLimited(Exception):
    def __init__(self, retry_after: float | None):
        self.retry_after = retry_after


def call_with_retry(fn, max_attempts=5, base=1.0, cap=30.0, sleep=print):
    for attempt in range(1, max_attempts + 1):
        try:
            return fn()
        except RateLimited as err:
            if attempt == max_attempts:
                raise
            if err.retry_after is not None:
                wait = err.retry_after                     # the service told us exactly
            else:
                wait = min(cap, base * 2 ** (attempt - 1)) * random.uniform(0.5, 1.0)
            sleep(f"attempt {attempt}: 429, waiting {wait:.2f}s")


random.seed(7)
responses = iter([RateLimited(None), RateLimited(None), RateLimited(4.0), "ok: answer text"])


def fake_model_call():
    item = next(responses)
    if isinstance(item, Exception):
        raise item
    return item


print(call_with_retry(fake_model_call))
attempt 1: 429, waiting 0.66s attempt 2: 429, waiting 1.15s attempt 3: 429, waiting 4.00s ok: answer text
Python
# in real code, configure the SDK instead of writing your own loop
client = project.get_openai_client(max_retries=5, timeout=60.0)
Tip

Retries hide problems. Log every 429 with the deployment name; if they are frequent, raise capacity or spread traffic across deployments rather than retrying harder.

Try it
  1. Run the Option 1 example, then change model to the catalog name of a model you have not deployed and read the 404.
  2. Stream a 300-word answer and measure time to first token with time.perf_counter().
  3. Add a priority field to the Ticket schema and check the model fills it.
Key takeaways
  • Use AIProjectClient(...).get_openai_client() for one keyless client; or the plain OpenAI SDK with base_url=<project>/openai/v1.
  • The model field is your deployment name.
  • Prefer the Responses API for new code; Chat Completions remains for legacy and some partner models.
  • Stream for user-facing text; use responses.parse with a schema when code consumes the output.
  • Handle 401/403/404/429 distinctly, honour retry-after, and back off with jitter.