Optimize & Operationalize GenAI Solutions
Making your GenAI solution production-ready. This topic covers generation parameters that control model behaviour, prompt engineering techniques for better responses, fine-tuning vs RAG tradeoffs, quota and throughput management, monitoring and tracing, and orchestrating multiple AI models together.
Uses: token, LLM, deployment name, messages array, chat.completions.create(), RAG, Prompt Flow, evaluation metrics. New concepts introduced below before any code.
Optimisation and operationalisation terms
A parameter (0.0 to 2.0) that controls how random or deterministic the model's output is. Low temperature (0.0–0.3) = more predictable, consistent responses. High temperature (0.8–2.0) = more creative, varied, sometimes unexpected responses. For factual tasks, use low temperature.
An alternative to temperature. Controls output diversity by limiting the model to tokens that collectively represent the top P% of probability mass. 0.1 = very focused, 1.0 = all tokens considered. Use temperature OR top_p — not both simultaneously.
The maximum number of tokens the model can generate in its response. Does not affect input tokens. Setting this too low cuts off the response mid-sentence. Setting it too high wastes tokens (you pay for unused capacity).
A value from -2.0 to 2.0 that penalises the model for using words it has already used in the response. Positive values (0.5–1.0) reduce repetition — the model is less likely to repeat the same phrases. Useful for longer outputs.
Similar to frequency_penalty but penalises any word that has appeared at all (not based on frequency). Encourages the model to introduce new topics and vocabulary. Useful for open-ended creative tasks.
A list of strings that, when generated, cause the model to stop.
For example: stop=["###"] — if the model outputs "###" it stops
immediately. Useful for structured outputs where you want the model to stop
at a specific boundary marker.
The quota limit for Azure OpenAI — the maximum number of tokens your deployment can process per minute. Exceeding TPM returns HTTP 429 (Too Many Requests). You can set TPM per deployment and request quota increases from Microsoft.
A reserved capacity unit for Azure OpenAI. Instead of pay-per-token, you reserve a fixed number of PTUs that guarantee consistent throughput. More expensive upfront but no throttling. Best for predictable high-volume production workloads.
Training a pre-existing model on your own dataset to make it better at a specific task or to adopt a specific writing style. Different from RAG — fine-tuning changes the model's weights permanently, while RAG provides context at query time. Fine-tuned models are also more expensive to run.
Asking the model to perform a task without providing any examples. Just a clear instruction: "Classify this email as spam or not spam." Works well for tasks the model already understands from training.
Providing 2–5 examples of the task in the prompt before the actual question. The model learns the pattern from the examples and applies it. More reliable than zero-shot for specialised formats or unusual tasks.
Instructing the model to "think step by step" before giving its final answer. This dramatically improves accuracy on complex reasoning tasks like maths, logic, and multi-step problems. The model externalises its reasoning process.
Recording the complete execution path of a Prompt Flow or multi-model pipeline — every step, every input, every output, every duration. Used for debugging, performance analysis, and cost attribution. Enabled in Foundry via Application Insights.
A pattern where the model evaluates its own output and iteratively improves it. The model generates an answer, then critiques that answer, then revises it based on the critique. Can produce higher-quality outputs than a single generation pass.
Coordinating multiple AI models or services to work together in a pipeline. For example: GPT-4o summarises a document → Azure AI Language extracts key entities → another GPT model generates a report. Each model is specialised; orchestration combines them.
Controlling how the model generates text
Every call to the Chat Completions API can include optional parameters that change how the model generates its response. The exam tests whether you know which parameter to use for which problem.
Think of the generation parameters as sliders on a mixing board. Temperature is the "creativity" dial — turn it up for jazz improvisation, turn it down for playing classical sheet music exactly. max_tokens is the track length limit. frequency_penalty is the "no repeating yourself" dial. stop sequences are the track markers where recording stops.
Temperature — the most important parameter
| Temperature Value | Behaviour | Best for |
|---|---|---|
0.0 | Fully deterministic — same prompt always gives the same answer | Factual Q&A, classification, extraction tasks where consistency is critical |
0.1 – 0.3 | Very focused, slightly varied | Technical writing, code generation, structured summaries |
0.7 | Balanced (Azure's default) — natural and somewhat varied | General conversation, customer service |
1.0 – 1.5 | Creative and varied outputs | Marketing copy, brainstorming, creative writing |
2.0 | Very random — may become incoherent | Rarely used in production — experimental only |
All parameters in a single Python call
import os
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_key = os.environ.get("AZURE_OPENAI_KEY"),
api_version = "2024-02-01"
)
response = client.chat.completions.create(
model = "my-gpt4o",
messages = [
{"role": "system", "content": "You are a product description writer."},
{"role": "user", "content": "Write a product description for wireless headphones."}
],
# ── Generation control parameters ────────────────────────────────────
temperature = 0.8,
# 0.8 = moderately creative (good for marketing copy)
# Range: 0.0 (deterministic) to 2.0 (very random)
# Use EITHER temperature OR top_p — not both
max_tokens = 300,
# Maximum tokens in the response
# Response is cut off if it reaches this limit
# Does NOT include input tokens — just output
frequency_penalty = 0.5,
# Reduces repetition of words already used in the response
# Range: -2.0 to 2.0 | 0 = no penalty | positive = less repetition
presence_penalty = 0.3,
# Encourages new topics/vocabulary
# Range: -2.0 to 2.0 | 0 = no penalty | positive = more novelty
stop = ["###", "END"],
# Stop generating if the model outputs "###" or "END"
# Useful for structured outputs with known end markers
n = 1,
# Number of response alternatives to generate
# n=1 is standard. n=3 generates 3 different responses — you choose the best
# Higher n costs proportionally more tokens
)
answer = response.choices[0].message.content
print(answer)
"Outputs are inconsistent for the same question — sometimes accurate, sometimes not."
→ Lower temperature (towards 0.0) for more consistency.
"The model keeps repeating the same phrases."
→ Increase frequency_penalty
"Responses are getting cut off before completing."
→ Increase max_tokens
"Need the model to stop generating after producing a JSON object."
→ Use stop sequences — e.g. stop=["```"]
Getting better responses through better prompts
Prompt engineering is the practice of crafting prompts that reliably produce the outputs you want. These are the techniques the exam tests.
No examples provided — just a clear instruction. Works for well-understood tasks. Fast and simple but may not produce the exact format you need.
User: Classify this review as positive, negative, or neutral: "The product arrived on time but the packaging was damaged."
Assistant: Neutral
You include 2–5 example input/output pairs in the prompt. The model learns the pattern and applies it to the new input. More reliable for structured or unusual output formats.
User: Examples:
Input: "My order #12345 hasn't arrived after 3 weeks."
Output: {"order_id": "12345", "issue": "delayed delivery", "urgency": "high"}
Input: "The item in order #99001 was broken when I opened it."
Output: {"order_id": "99001", "issue": "damaged item", "urgency": "medium"}
Now process: "I never received my order #55555 from last month."
Add "Think step by step" or "Let's reason through this" to the prompt. The model externalises its reasoning before giving a final answer. Dramatically improves accuracy on reasoning tasks.
Assistant: Let me work through this step by step.
Step 1: Current capacity = 3 × 150 = 450 req/min
Step 2: New traffic = 450 × 1.40 = 630 req/min
Step 3: Servers needed = 630 ÷ 150 = 4.2 → round up to 5
Answer: 5 servers
The system message is the most powerful control you have over the model. A well-crafted system message defines: persona, tone, knowledge scope, output format, what to refuse, and how to handle edge cases.
"You are a financial advisor assistant for Contoso Bank. Rules: (1) Only answer questions about Contoso Bank products. (2) Never speculate about stock prices or investment returns. (3) Always end with 'For personalised advice, speak with a Contoso advisor.' (4) If asked about competitor products, politely decline and focus on Contoso offerings. (5) Format responses as bullet points when listing features."
Ask the model to help design the prompt itself: "Write a system prompt for an AI that helps CIBC customers track their spending." The model generates a prompt you then use in your application.
When to fine-tune a model and how it works
Fine-tuning customises a pre-trained model by training it further on your own dataset. The model's internal weights are updated to make it better at specific tasks or to adopt a specific writing style or domain knowledge.
Fine-tuning vs RAG — the most important decision
| Decision factor | Use RAG | Use Fine-Tuning |
|---|---|---|
| Goal | Ground responses in specific, up-to-date documents | Teach the model a new style, format, or domain-specific behaviour |
| Data freshness | Documents can be updated without retraining | Model must be retrained when data changes |
| Factual accuracy | High — answers traced to source documents | Lower — model may still hallucinate even after fine-tuning |
| Cost | Lower upfront, per-query retrieval cost | Training cost + higher inference cost (fine-tuned models cost more per token) |
| When to use | Enterprise Q&A, document search, any use case where data changes regularly | Teaching specific tone/format, simplifying prompts, consistent output structure |
"A company wants the AI to answer questions using their current product catalogue, which updates weekly."
→ RAG — data changes frequently, fine-tuning every week is impractical.
"A company wants the AI to always respond in their brand's unique tone and use their specific terminology, without needing a long system prompt every time."
→ Fine-Tuning — teaching style and format is what fine-tuning is for.
"A company needs their AI to avoid making factual errors about competitors."
→ RAG — fine-tuning doesn't eliminate hallucination; grounding does.
Fine-tuning training data format
Azure OpenAI fine-tuning requires training data in JSONL format (one JSON object per line). Each object is a conversation with system, user, and assistant messages — showing the model the style of response you want.
# Fine-tuning training data format — JSONL (JSON Lines)
# Each line is one training example — a complete conversation
# The model learns to respond to "user" messages the way "assistant" does in your examples
# Line 1: training example 1
{"messages": [
{"role": "system", "content": "You are a concise technical support agent."},
{"role": "user", "content": "My printer won't connect to WiFi."},
{"role": "assistant", "content": "Try: 1) Restart router. 2) Forget and rejoin network. 3) Update printer firmware."}
]}
# Line 2: training example 2
{"messages": [
{"role": "system", "content": "You are a concise technical support agent."},
{"role": "user", "content": "I can't login — password reset email not arriving."},
{"role": "assistant", "content": "Check spam folder. Whitelist noreply@company.com. Try alternate email if registered."}
]}
# Minimum: 10 training examples (recommended: 50-100+)
# More examples = better fine-tuning results
# Upload this file to Foundry: your project → Fine-tuning → + New fine-tune job
Managing scale and avoiding throttling
TPM (Tokens Per Minute) is like a tap with a flow rate limit.
You can use as much water (tokens) as you want, but only up to the rate
limit. If you try to use more, you get throttled (HTTP 429) until the
next minute resets your allowance.
PTU (Provisioned Throughput Units) is like paying to have
a dedicated pipe installed. Your pipe is always available — no sharing,
no throttling. But you pay for the pipe whether you use it or not.
| Standard (TPM-based) | Provisioned Managed (PTU) | |
|---|---|---|
| Billing | Pay per token used | Fixed hourly cost regardless of usage |
| Throughput | Variable — may throttle at peak | Guaranteed consistent throughput |
| Throttling | Yes — HTTP 429 if TPM exceeded | No throttling — reserved capacity |
| Best for | Variable/unpredictable traffic, dev/test | Production workloads with predictable high traffic |
| Minimum commitment | None | Minimum 1 hour reservation |
Handling HTTP 429 (throttling) in Python
New Python concept: try/except — a way to handle errors gracefully instead of crashing. Explained fully in comments.
import os, time
from openai import AzureOpenAI, RateLimitError
client = AzureOpenAI(
azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_key = os.environ.get("AZURE_OPENAI_KEY"),
api_version = "2024-02-01"
)
# New concept: try/except ────────────────────────────────────────────
# "try:" — attempt to run this code
# "except SomeError:" — if THIS specific error occurs, run this instead
# This prevents your program from crashing when an error happens
# It's like a safety net — "try this, but if it fails, do that"
MAX_RETRIES = 3 # How many times to retry before giving up
for attempt in range(MAX_RETRIES): # range(3) = [0, 1, 2] — loops 3 times
try:
# Try to call the API
response = client.chat.completions.create(
model = "my-gpt4o",
messages = [{"role": "user", "content": "Explain quantum computing simply."}]
)
# If the call succeeded, print the result and break out of the loop
print(response.choices[0].message.content)
break # "break" exits the loop immediately — success!
except RateLimitError as e:
# RateLimitError happens when we exceed our TPM quota (HTTP 429)
# "as e" gives us access to the error details
if attempt < MAX_RETRIES - 1: # If this isn't our last attempt...
wait_seconds = 2 ** attempt # Exponential backoff: 1s, 2s, 4s
print(f"Rate limited. Waiting {wait_seconds}s before retry {attempt+1}...")
time.sleep(wait_seconds) # time.sleep() pauses the program for N seconds
else:
# We've used all our retries — give up and report the error
print(f"Failed after {MAX_RETRIES} attempts: {e}")
Keeping your GenAI solution healthy in production
From Part 1 Topic 4 you know Azure Monitor and Application Insights. GenAI solutions add additional monitoring needs specific to LLM behaviour.
Key metrics to monitor for GenAI
Prompt tokens + completion tokens = total cost. Monitor to detect runaway prompts (huge inputs) or verbose outputs driving up costs. Available in response.usage.
Time from request sent to first token received (TTFT — Time to First Token) and total response time. GPT-4o is slower than GPT-3.5-turbo. Monitor to detect degradation.
429 (throttling), 400 (bad request), 500 (service error). Spikes in 429s indicate you need more TPM or should switch to PTU.
Thumbs up/down ratings from users on AI responses. Collect these to identify which query types perform poorly and guide further prompt or RAG improvements.
Enabling tracing in Foundry
Tracing records the complete execution of a Prompt Flow — every node, every input/output, every duration. This is invaluable for debugging.
ai.azure.com → your project → Settings → Tracing → Connect an Application Insights resource → Save.
Open your Prompt Flow → Run settings → Enable Tracing. Every run now records complete node-by-node telemetry.
Azure portal → Application Insights → Investigate → Transaction search → Find your flow runs. See each node, its inputs, outputs, and duration in a waterfall view.
Self-improvement and multi-model pipelines
Model Reflection — the model critiques itself
Model Reflection is a pattern where you ask the model to generate a response, then ask it to critique that response, then generate an improved version. This iterative loop produces higher quality outputs than a single generation pass.
import os
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_key=os.environ.get("AZURE_OPENAI_KEY"),
api_version="2024-02-01"
)
MODEL = "my-gpt4o"
# ── Step 1: Generate initial response ─────────────────────────────────
user_task = "Write a 3-sentence summary of quantum computing for a non-technical audience."
initial = client.chat.completions.create(
model = MODEL,
messages = [{"role": "user", "content": user_task}]
)
initial_response = initial.choices[0].message.content
print(f"INITIAL:\n{initial_response}\n")
# ── Step 2: Ask the model to critique its own response ────────────────
# We include the initial response in the conversation history
# so the model can see what it generated and evaluate it
critique = client.chat.completions.create(
model = MODEL,
messages = [
{"role": "user", "content": user_task},
{"role": "assistant", "content": initial_response}, # The initial response
{"role": "user", "content": "Critique this response. Is it accurate? "
"Is it truly non-technical? What could be improved?"}
]
)
critique_text = critique.choices[0].message.content
print(f"CRITIQUE:\n{critique_text}\n")
# ── Step 3: Generate improved response based on the critique ──────────
improved = client.chat.completions.create(
model = MODEL,
messages = [
{"role": "user", "content": user_task},
{"role": "assistant", "content": initial_response},
{"role": "user", "content": "Critique: " + critique_text},
{"role": "user", "content": "Now write an improved version addressing the critique."}
]
)
improved_response = improved.choices[0].message.content
print(f"IMPROVED:\n{improved_response}")
Orchestration — multiple models working together
Real enterprise AI solutions often combine multiple models and services, each doing what it does best. The exam tests understanding of when to combine models and how.
Step 1 — Whisper (Azure OpenAI): Transcribe the customer's voice call to text.
Step 2 — Azure AI Language: Extract intent (complaint/enquiry/cancellation) and key entities (account number, product name) from the transcript.
Step 3 — Azure AI Search (RAG): Retrieve relevant policy documents based on the intent.
Step 4 — GPT-4o: Generate a personalised, grounded response for the agent to read to the customer.
Step 5 — Azure AI Content Safety: Screen the generated response before delivery.
Each model is specialised. Prompt Flow orchestrates all 5 steps.
Optimisation — what the exam specifically tests
1. Temperature 0 = deterministic — Same prompt always gives
same output. For factual tasks requiring consistency, use temperature 0.
2. Use temperature OR top_p — not both — They are alternatives.
Microsoft recommends altering one but not both simultaneously.
3. Fine-tuning does NOT eliminate hallucination — Fine-tuning
teaches style and format, not facts. For factual accuracy, use RAG.
4. Fine-tuning JSONL format — Training data must be in JSONL
with messages arrays (system + user + assistant). Minimum 10 examples.
5. PTU for production, TPM for dev — If a scenario mentions
"predictable high traffic" or "no throttling" → PTU (Provisioned).
If it mentions "variable traffic" or "cost optimisation" → Standard TPM.
6. Chain-of-thought for complex reasoning — Add "think step
by step" to any maths, logic, or multi-step analysis task. This dramatically
improves accuracy and is directly tested.
Test your understanding
5 questions — select an answer to see the explanation immediately.
Which parameter change will most effectively make responses consistent?
B is correct. Temperature controls randomness. High temperature = varied, creative outputs. Low temperature (near 0) = deterministic, consistent outputs. Setting temperature close to 0 will make the model give the same response to the same question every time — exactly what's needed for consistent factual customer service answers. Increasing temperature (option A) would make things worse. max_tokens (option C) controls response length, not consistency. frequency_penalty (option D) reduces word repetition within one response, not across separate responses.