Session 3: Creating Task-Specific AI Utilities
Synopsis
Focuses on designing focused applications such as document summarizers, content generators, coding helpers, and data extraction tools. Learners understand when narrow-purpose applications outperform general assistants.
Session Content
Session 3: Creating Task-Specific AI Utilities
Session Overview
In this session, learners move from simple prompt experiments to building reusable, task-specific AI utilities in Python. The focus is on designing small, reliable helper functions that wrap LLM capabilities for real development tasks such as summarization, rewriting, classification, and structured extraction.
By the end of this session, learners will be able to:
- Identify when a task should be turned into a reusable AI utility
- Design prompts for predictable, narrow-purpose behaviors
- Build Python helper functions around the OpenAI Responses API
- Return both plain-text and structured outputs from utilities
- Add lightweight validation and error handling for safer usage
- Combine multiple utilities into a simple workflow
Agenda
- Why task-specific utilities matter
- Patterns for reusable AI helpers
- Building a summarizer utility
- Building a classifier utility
- Building a structured extraction utility
- Composing utilities into a mini workflow
- Wrap-up and practice ideas
1. Why Create Task-Specific AI Utilities?
A common beginner mistake in GenAI development is to use one giant prompt for every task. This often leads to:
- inconsistent outputs
- hard-to-debug behavior
- repeated prompt code
- poor maintainability
- difficulty testing
A better pattern is to create small, focused utilities such as:
summarize_text(...)classify_ticket(...)extract_contact_info(...)rewrite_as_bullet_points(...)
These utilities act like normal Python functions, but internally call an LLM.
Benefits
- Reusability across scripts and applications
- Better prompt clarity
- Easier testing and iteration
- More predictable output formats
- Simpler debugging
Good candidates for AI utilities
Use a task-specific AI utility when the task involves:
- natural language understanding
- information extraction from messy text
- language transformation
- semantic classification
- summarization
- formatting or rewriting
Not ideal candidates
Avoid using an LLM utility for:
- deterministic arithmetic
- exact sorting/filtering logic
- rule-based validation that Python can do directly
- anything requiring guaranteed factual correctness without verification
2. Design Principles for AI Utilities
When creating a utility, keep the function narrow and explicit.
Key design principles
1. One utility, one responsibility
Bad: - A function that summarizes, classifies, rewrites, and extracts all at once
Good: - Separate focused functions
2. Give the model a clear role and task
Example: - “You are a concise technical summarizer.” - “Classify the support ticket into one category.”
3. Constrain the output
Ask for: - one sentence - one label from a fixed list - JSON only - bullet points only
4. Post-process in Python
Let Python handle: - validation - fallback logic - defaults - formatting - error handling
5. Test with realistic inputs
A utility should be tried on: - short inputs - long inputs - messy inputs - ambiguous inputs - edge cases
3. Environment Setup
Install the OpenAI Python SDK:
pip install openai python-dotenv
Create a .env file:
OPENAI_API_KEY=your_api_key_here
4. Base Setup for This Session
We will use a shared client setup across all exercises.
import os
from dotenv import load_dotenv
from openai import OpenAI
# Load environment variables from .env
load_dotenv()
# Read API key from environment
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("OPENAI_API_KEY is not set. Add it to your .env file.")
# Create OpenAI client
client = OpenAI(api_key=api_key)
5. Building a Summarizer Utility
A summarizer is one of the simplest useful utilities.
Goal
Create a function that: - accepts raw text - returns a concise summary - can be reused in other scripts
Example Utility
import os
from dotenv import load_dotenv
from openai import OpenAI
# Load .env values
load_dotenv()
# Create the client once and reuse it
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def summarize_text(text: str, max_sentences: int = 2) -> str:
"""
Summarize the given text into a small number of sentences.
Args:
text: The input text to summarize.
max_sentences: Maximum number of sentences in the summary.
Returns:
A concise summary string.
"""
if not text.strip():
raise ValueError("Input text must not be empty.")
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": (
"You are a concise summarization assistant. "
"Summarize clearly and accurately."
),
},
{
"role": "user",
"content": (
f"Summarize the following text in no more than "
f"{max_sentences} sentences:\n\n{text}"
),
},
],
)
return response.output_text.strip()
if __name__ == "__main__":
sample_text = """
Retrieval-augmented generation (RAG) is a pattern that combines language models
with external knowledge retrieval. Instead of relying entirely on the model's
internal training data, relevant documents are fetched at runtime and included
in the prompt. This helps improve factual grounding, reduce hallucinations,
and provide more up-to-date answers.
"""
summary = summarize_text(sample_text, max_sentences=2)
print("Summary:")
print(summary)
Example Output
Summary:
Retrieval-augmented generation (RAG) combines a language model with external document retrieval at runtime. This improves factual grounding, reduces hallucinations, and helps generate more up-to-date responses.
Discussion
This is a good first utility because it: - has a clear purpose - produces a short output - is easy to evaluate manually
6. Hands-On Exercise 1: Build a Text Rewriter Utility
Objective
Create a utility that rewrites technical text as beginner-friendly bullet points.
What learners will build
A function:
rewrite_as_bullets(text: str) -> str
Starter Code
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def rewrite_as_bullets(text: str) -> str:
"""
Rewrite technical text into beginner-friendly bullet points.
Args:
text: Raw technical text.
Returns:
A bullet-point version of the text.
"""
if not text.strip():
raise ValueError("Input text must not be empty.")
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": (
"You are a helpful assistant that rewrites technical content "
"for beginners. Use simple language and bullet points."
),
},
{
"role": "user",
"content": (
"Rewrite the following text as beginner-friendly bullet points. "
"Keep the meaning accurate and the tone simple.\n\n"
f"{text}"
),
},
],
)
return response.output_text.strip()
if __name__ == "__main__":
technical_text = """
Vector embeddings are numerical representations of data in a high-dimensional
space. They allow semantically similar items to be located near each other,
which makes them useful for search, recommendation, and retrieval tasks.
"""
bullets = rewrite_as_bullets(technical_text)
print("Beginner-friendly bullets:")
print(bullets)
Example Output
Beginner-friendly bullets:
- Vector embeddings turn data into lists of numbers.
- Similar pieces of data end up close to each other in this number space.
- This helps with tasks like search, recommendations, and finding relevant information.
Suggested learner improvements
- Add a
target_audienceparameter - Add a
max_bulletsparameter - Add a fallback if the response is empty
7. Building a Classifier Utility
Classification is another strong use case for a task-specific AI utility.
Example use cases
- support ticket routing
- intent detection
- sentiment labeling
- content moderation categories
- document type classification
Important principle
For classification, always constrain the model to a known list of labels.
Example Utility
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
VALID_CATEGORIES = {"billing", "technical", "account", "other"}
def classify_support_ticket(ticket_text: str) -> str:
"""
Classify a support ticket into one of the allowed categories.
Args:
ticket_text: The raw support request text.
Returns:
One category label as a string.
"""
if not ticket_text.strip():
raise ValueError("Ticket text must not be empty.")
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": (
"You are a support ticket classifier. "
"Return only one category label."
),
},
{
"role": "user",
"content": (
"Classify the following support ticket into exactly one of these "
"categories: billing, technical, account, other.\n"
"Return only the label and nothing else.\n\n"
f"Ticket:\n{ticket_text}"
),
},
],
)
label = response.output_text.strip().lower()
# Validate model output
if label not in VALID_CATEGORIES:
return "other"
return label
if __name__ == "__main__":
ticket = "I updated my password, but I still can't log into my dashboard."
category = classify_support_ticket(ticket)
print(f"Category: {category}")
Example Output
Category: account
Why validation matters
Even with good prompts, models may return:
- Account
- account.
- This looks like account
- something unexpected
Validation in Python keeps your application safer and more predictable.
8. Hands-On Exercise 2: Improve the Classifier
Objective
Extend the classifier to return both: - the category - a short explanation
Target function
classify_ticket_with_reason(ticket_text: str) -> dict
Requirements
Return a Python dictionary like:
{
"category": "account",
"reason": "The user cannot log in after changing their password."
}
Recommended approach
Ask the model for JSON output and parse it in Python.
Example Solution
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
VALID_CATEGORIES = {"billing", "technical", "account", "other"}
def classify_ticket_with_reason(ticket_text: str) -> dict:
"""
Classify a support ticket and return a short reason.
Args:
ticket_text: Raw support ticket text.
Returns:
A dictionary with keys:
- category
- reason
"""
if not ticket_text.strip():
raise ValueError("Ticket text must not be empty.")
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": (
"You are a support ticket classifier. "
"Return valid JSON only."
),
},
{
"role": "user",
"content": (
"Classify the support ticket into one of these categories: "
"billing, technical, account, other.\n"
"Return JSON only in this format:\n"
'{"category": "one_label", "reason": "short explanation"}\n\n'
f"Ticket:\n{ticket_text}"
),
},
],
)
raw_text = response.output_text.strip()
try:
result = json.loads(raw_text)
except json.JSONDecodeError:
return {
"category": "other",
"reason": "Model did not return valid JSON.",
}
category = str(result.get("category", "other")).lower().strip()
reason = str(result.get("reason", "")).strip()
if category not in VALID_CATEGORIES:
category = "other"
if not reason:
reason = "No reason provided."
return {
"category": category,
"reason": reason,
}
if __name__ == "__main__":
ticket = "My card was charged twice for the same subscription this month."
result = classify_ticket_with_reason(ticket)
print("Classification result:")
print(result)
Example Output
Classification result:
{'category': 'billing', 'reason': 'The user is reporting a duplicate charge for their subscription.'}
9. Building a Structured Extraction Utility
Structured extraction turns messy text into machine-usable data.
Example use cases
- extracting contact info from emails
- pulling action items from meeting notes
- identifying due dates from documents
- parsing product requirements into fields
Example Utility: Extract Contact Info
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def extract_contact_info(text: str) -> dict:
"""
Extract contact information from free-form text.
Args:
text: Input text that may contain a person's contact information.
Returns:
A dictionary with keys:
- name
- email
- company
"""
if not text.strip():
raise ValueError("Input text must not be empty.")
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": (
"You extract structured contact information from text. "
"Return valid JSON only."
),
},
{
"role": "user",
"content": (
"Extract the following fields from the text if available: "
"name, email, company.\n"
"Return JSON only using this schema:\n"
'{"name": null, "email": null, "company": null}\n\n'
f"Text:\n{text}"
),
},
],
)
raw_text = response.output_text.strip()
try:
result = json.loads(raw_text)
except json.JSONDecodeError:
return {"name": None, "email": None, "company": None}
return {
"name": result.get("name"),
"email": result.get("email"),
"company": result.get("company"),
}
if __name__ == "__main__":
email_text = """
Hello,
I'm Priya Natarajan from BrightScale Labs.
You can reach me at priya@brightscale.ai for the proposal discussion.
Best,
Priya
"""
contact = extract_contact_info(email_text)
print("Extracted contact info:")
print(contact)
Example Output
Extracted contact info:
{'name': 'Priya Natarajan', 'email': 'priya@brightscale.ai', 'company': 'BrightScale Labs'}
Lessons
Structured extraction is useful because it lets downstream Python code work with clean fields instead of raw text.
10. Hands-On Exercise 3: Extract Action Items from Meeting Notes
Objective
Create a utility that extracts action items from meeting notes.
Desired output
A list of dictionaries like:
[
{"task": "Prepare API demo", "owner": "Asha", "due_date": "Friday"},
{"task": "Review onboarding flow", "owner": "Marcus", "due_date": None}
]
Example Solution
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def extract_action_items(notes: str) -> list[dict]:
"""
Extract action items from meeting notes.
Args:
notes: Free-form meeting notes.
Returns:
A list of dictionaries with:
- task
- owner
- due_date
"""
if not notes.strip():
raise ValueError("Meeting notes must not be empty.")
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": (
"You extract action items from meeting notes. "
"Return valid JSON only."
),
},
{
"role": "user",
"content": (
"Extract action items from the meeting notes.\n"
"Return JSON only as a list of objects with this schema:\n"
'[{"task": "string", "owner": "string or null", "due_date": "string or null"}]\n\n'
f"Meeting notes:\n{notes}"
),
},
],
)
raw_text = response.output_text.strip()
try:
items = json.loads(raw_text)
except json.JSONDecodeError:
return []
if not isinstance(items, list):
return []
cleaned_items = []
for item in items:
if not isinstance(item, dict):
continue
cleaned_items.append(
{
"task": item.get("task"),
"owner": item.get("owner"),
"due_date": item.get("due_date"),
}
)
return cleaned_items
if __name__ == "__main__":
notes = """
Team sync notes:
- Asha will prepare the API demo by Friday.
- Marcus should review the onboarding flow.
- Jenna is responsible for drafting the release email by next Tuesday.
"""
action_items = extract_action_items(notes)
print("Action items:")
for item in action_items:
print(item)
Example Output
Action items:
{'task': 'Prepare the API demo', 'owner': 'Asha', 'due_date': 'Friday'}
{'task': 'Review the onboarding flow', 'owner': 'Marcus', 'due_date': None}
{'task': 'Draft the release email', 'owner': 'Jenna', 'due_date': 'next Tuesday'}
11. Composing Utilities into a Mini Workflow
A powerful next step is combining several utilities.
Example workflow
Given a support email: 1. summarize it 2. classify it 3. extract contact information
This creates a small AI-powered processing pipeline.
Example Workflow Code
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
VALID_CATEGORIES = {"billing", "technical", "account", "other"}
def summarize_text(text: str, max_sentences: int = 2) -> str:
"""Return a concise summary of the text."""
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": "You are a concise summarization assistant.",
},
{
"role": "user",
"content": (
f"Summarize the following text in no more than "
f"{max_sentences} sentences:\n\n{text}"
),
},
],
)
return response.output_text.strip()
def classify_support_ticket(ticket_text: str) -> str:
"""Classify a support ticket into a fixed set of categories."""
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": "You are a support ticket classifier. Return only one label.",
},
{
"role": "user",
"content": (
"Classify this ticket into exactly one of: "
"billing, technical, account, other.\n"
"Return only the label.\n\n"
f"{ticket_text}"
),
},
],
)
label = response.output_text.strip().lower()
return label if label in VALID_CATEGORIES else "other"
def extract_contact_info(text: str) -> dict:
"""Extract name, email, and company as JSON."""
response = client.responses.create(
model="gpt-5.4-mini",
input=[
{
"role": "system",
"content": "Extract contact details. Return valid JSON only.",
},
{
"role": "user",
"content": (
"Extract name, email, and company from the following text.\n"
'Return JSON only in this format: {"name": null, "email": null, "company": null}\n\n'
f"{text}"
),
},
],
)
try:
return json.loads(response.output_text.strip())
except json.JSONDecodeError:
return {"name": None, "email": None, "company": None}
def process_support_email(email_text: str) -> dict:
"""
Process a support email with multiple AI utilities.
Args:
email_text: Raw incoming support email text.
Returns:
A dictionary containing summary, category, and contact info.
"""
if not email_text.strip():
raise ValueError("Email text must not be empty.")
return {
"summary": summarize_text(email_text, max_sentences=2),
"category": classify_support_ticket(email_text),
"contact": extract_contact_info(email_text),
}
if __name__ == "__main__":
email_text = """
Hi support team,
My name is Daniel Lee from Northwind Analytics.
I was charged twice for our monthly subscription, and I need help fixing the billing issue.
You can contact me at daniel.lee@northwind.io.
Thanks,
Daniel
"""
result = process_support_email(email_text)
print("Processed email:")
print(json.dumps(result, indent=2))
Example Output
{
"summary": "Daniel Lee from Northwind Analytics reports being charged twice for a monthly subscription and requests billing support.",
"category": "billing",
"contact": {
"name": "Daniel Lee",
"email": "daniel.lee@northwind.io",
"company": "Northwind Analytics"
}
}
12. Best Practices for Production-Friendly Utilities
Even small AI utilities benefit from some engineering discipline.
Prompt best practices
- Be explicit about the task
- Define the output format clearly
- Keep prompts focused
- Prefer fixed labels for classification
- Use examples if the task is ambiguous
Python best practices
- Validate input
- Validate output
- Use defaults and fallbacks
- Keep AI calls inside reusable functions
- Avoid duplicating prompt logic everywhere
- Log raw responses during debugging
- Keep business rules in Python, not only in prompts
Practical limitations to remember
- LLM outputs are probabilistic
- Structured output requests can still fail
- Extracted facts may need verification
- Utility quality depends heavily on prompt clarity
13. Mini Challenge
Build one additional AI utility of your own.
Suggested ideas
extract_keywords(text)generate_release_notes(changelog_text)classify_review_sentiment(review_text)rewrite_email_politely(email_text)extract_deadlines(text)
Challenge requirements
Your utility should:
- do one focused task
- use gpt-5.4-mini
- use the OpenAI Responses API
- validate inputs
- handle malformed output gracefully
- include a short demo in __main__
14. Recap
In this session, learners practiced creating narrow, reusable AI utilities for practical development tasks.
Main takeaways
- Task-specific utilities are more reliable than one giant prompt
- Small helper functions make AI features reusable and testable
- Prompt constraints improve consistency
- Python validation is essential for safe downstream use
- Utilities can be composed into simple AI workflows
15. Suggested 45-Minute Session Breakdown
Part 1: Theory and Concepts (10 minutes)
- Why task-specific AI utilities matter
- Good utility design patterns
- When to use plain text vs structured output
Part 2: Guided Demo (10 minutes)
- Build
summarize_text(...) - Discuss prompt design and validation
Part 3: Hands-On Exercise 1 (8 minutes)
- Build
rewrite_as_bullets(...)
Part 4: Guided Demo 2 (7 minutes)
- Build
classify_support_ticket(...) - Add output validation
Part 5: Hands-On Exercise 2 (5 minutes)
- Build
classify_ticket_with_reason(...)
Part 6: Hands-On Exercise 3 (5 minutes)
- Build
extract_action_items(...)
Useful Resources
- OpenAI Responses API migration guide: https://developers.openai.com/api/docs/guides/migrate-to-responses
- OpenAI API docs overview: https://platform.openai.com/docs
- OpenAI Python SDK: https://github.com/openai/openai-python
- Python
jsonmodule docs: https://docs.python.org/3/library/json.html python-dotenvdocs: https://pypi.org/project/python-dotenv/
Homework
- Create a utility that extracts deadlines from project updates.
- Create a utility that classifies product reviews into
positive,neutral, ornegative. - Refactor today’s utilities into a single Python module named
ai_utils.py. - Add simple logging so you can inspect raw model outputs during debugging.
- Test each utility with at least 5 different inputs and record failure cases.
End of Session
In the next session, learners can build on these utilities to create multi-step AI workflows and more agent-like behaviors.
Back to Chapter | Back to Master Plan | Previous Session | Next Session