Logo
CloudWithSingh
Back to all field notes
Workflow Guide
Azure
Intermediate

5 AI Projects a Cloud Engineer Can Ship in a Weekend

Five Azure AI projects you can build and demo in a weekend each: chat over your own documents, a support bot with human handoff, a cloud bill explainer, an MCP server, and an invoice reader. Services, build steps, code, and the gotchas that cost me time.

Parveen Singh
October 6, 2026
17 min read
Prerequisites:An Azure account with a subscription you can create resources inAzure CLI, Azure Developer CLI (azd), and Azure Functions Core Tools installedPython and a GitHub account
TLDR

Five AI projects, each small enough for one weekend and each backed by a real Azure service: chat over your own PDFs (AI Search + a Foundry model), a support bot on Azure Functions that hands off to a person, a cloud bill explainer on the Cost Management API, a remote MCP server on Azure Functions, and an invoice reader on Document Intelligence. Every section has the services, the steps, the code that matters, and what to put in the README so an interviewer can open it and try it.

🎯 The five at a glance

#ProjectCore servicesRough build timeWhat it shows an interviewer
1Chat over your documentsBlob Storage, AI Search, Foundry chat model3 to 4 hoursYou can ground a model in your own data and cite sources
2Support bot with handoffAzure Functions, Foundry chat model, Storage Queue2 hoursYou design for the model being wrong
3Cloud bill explainerCost Management Query API, Foundry chat model1 to 2 hoursYou can turn real billing data into something a manager reads
4MCP serverAzure Functions (MCP extension), Entra auth2 hoursYou can make a tool callable by an AI assistant, securely
5Invoice readerBlob Storage, Document Intelligence, Azure Functions2 to 3 hoursYou can build an event-driven pipeline that extracts structured data

✅ Rules for all five

RuleWhy
Set a budget alert on the subscription before you deploy anythingAI Search and model deployments are the two things that surprise people
Use managed identity, never an API key in code or app settingsIt is the first thing a reviewer looks for, and it is easier once you do it
One resource group per projectClean teardown, clean cost view
Pin region once and reuse itModel availability varies by region, and cross-region calls add latency
README: architecture in one diagram, how to run it, and one thing that brokeThe broken thing is the part that sounds like real work

1️⃣ Project 1: Chat over your own documents

Load PDFs into Azure AI Search and answer questions about them with a Foundry chat model, with citations.

The pattern is retrieval augmented generation (RAG): split documents into chunks, store them in a search index, pull the best chunks for a question, and give those chunks to the model as context.

🏗️ Architecture

LayerService
DocumentsBlob Storage container with 5 to 10 PDFs you wrote or own
Search and indexAzure AI Search, chunking and embeddings built into the indexer
ModelsA small chat model and an embedding model deployed in a Microsoft Foundry project (called Azure AI Foundry in older docs)
AppThe Microsoft sample app, or your own thin API

🛠️ Two routes

RouteBest forTime
A: Start from the sampleGetting a working app with a UI, then making it yoursUnder an hour to deploy, then your own data
B: Build the pipeline in the portalUnderstanding every moving part3 to 4 hours

Route A

azd init -t azure-search-openai-demo
azd auth login
azd up

The repo is Azure-Samples/azure-search-openai-demo. Swap its sample PDFs for your own by following the README section on adding documents, then redeploy. The portfolio value is in what you change: your documents, your system prompt, and a README that explains each component.

Route B

  1. Create a resource group, a storage account, and a container. Upload your PDFs.
  2. Create an Azure AI Search service. Use the Free tier for a handful of PDFs. A Basic tier bills while it exists, so delete it when you finish.
  3. In a Foundry project, deploy a small chat model and an embedding model.
  4. In the AI Search portal, run the Import data wizard against the container with vectorization on. It creates the data source, skillset, index, and indexer.
  5. Assign roles (below), then open the chat playground, choose Add your data, pick your index, set the search type to Semantic, and authenticate with the system assigned managed identity.
  6. Set a system prompt that forces citations, then test.
Answer the question based on the context below. Be specific and cite the source file name in brackets for each fact.

🔐 Roles you will need

WhoRoleOn what
You, in the playgroundCognitive Services OpenAI UserThe Azure OpenAI resource
The Azure OpenAI managed identitySearch Service ContributorThe AI Search service
The Azure OpenAI managed identitySearch Index Data ReaderThe AI Search service

🧯 When it breaks

SymptomLikely causeFix
PermissionDenied in the playgroundYour user is missing the OpenAI User roleAssign Cognitive Services OpenAI User on the OpenAI resource
403 reading /indexes/{name}The OpenAI managed identity cannot read the indexAssign Search Service Contributor on the search service
Answers with no citationsWrong search type or semantic configurationSelect Semantic and match the configuration name
Document count is zero after the wizardIndexer run failed or the path is wrongCheck the indexer run history for the error
Create Your First Azure AI Search Index
Hands-on Lab

Create Your First Azure AI Search Index

The index is the half of RAG people skip. Build one by hand before you put a model on top of it.

cloudlearn.ioStart the lab

2️⃣ Project 2: A support bot that knows when to hand off

An Azure Function that answers from a short FAQ, and passes the question to a person when the model is not sure.

The handoff is the project. Anyone can wire a model to an endpoint. A bot that admits it does not know is the one a business would actually deploy.

🏗️ Architecture

LayerService
EndpointAzure Functions, HTTP trigger, Python v2 model
ModelFoundry chat model, called with the function's managed identity
HandoffStorage Queue output binding, which a person or a second function reads

🛠️ Build steps

  1. Create a function app and deploy a chat model in a Foundry project.
  2. Turn on the function app's system assigned identity and give it Cognitive Services OpenAI User on the OpenAI resource.
  3. Add app settings for the endpoint and the deployment name.
  4. Write the function below, test with func start, then publish.
az functionapp identity assign -g rg-support-bot -n func-support-bot
az role assignment create \
  --assignee <principalId-from-previous-command> \
  --role "Cognitive Services OpenAI User" \
  --scope <openai-resource-id>

💻 The function

import json, os
import azure.functions as func
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import AzureOpenAI
 
app = func.FunctionApp()
 
client = AzureOpenAI(
    azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
    azure_ad_token_provider=get_bearer_token_provider(
        DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
    ),
    api_version="2024-10-21",
)
 
FAQ = open("faq.md").read()
SYSTEM = (
    "You are a support assistant. Answer only from the FAQ below. "
    'Reply as JSON: {"answer": string, "confident": boolean}. '
    "Set confident to false if the FAQ does not clearly answer the question.\n\n"
    + FAQ
)
 
@app.route(route="ask", methods=["POST"], auth_level=func.AuthLevel.FUNCTION)
@app.queue_output(arg_name="handoff", queue_name="handoff", connection="AzureWebJobsStorage")
def ask(req: func.HttpRequest, handoff: func.Out[str]) -> func.HttpResponse:
    question = req.get_json()["question"]
    r = client.chat.completions.create(
        model=os.environ["CHAT_DEPLOYMENT"],  # the deployment name, not the model name
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": question},
        ],
        response_format={"type": "json_object"},
        temperature=0,
    )
    out = json.loads(r.choices[0].message.content)
    if not out.get("confident"):
        handoff.set(question)  # a person picks it up from the queue
        return func.HttpResponse(json.dumps({"handoff": True}), mimetype="application/json")
    return func.HttpResponse(
        json.dumps({"handoff": False, "answer": out["answer"]}), mimetype="application/json"
    )

🔁 Make the handoff real

OptionEffortResult
Queue onlyNoneQuestions pile up in handoff, which you can read in Storage Explorer
Second function with a queue triggerLowPosts the question to Teams or email
Log handoff rateLowA number you can put in the README: how often the bot said "I don't know"
Sample the confident answersLowThe model's own confident flag is a weak signal, so read a handful each week to see whether it means anything

Before you share the URL, check the auth level.

3️⃣ Project 3: A cloud bill explainer

Pull last month's costs from Cost Management and have a model explain the top three line items in plain English.

This is the quickest of the five and the one a hiring manager understands immediately, because everyone has stared at a bill and wondered what it meant.

🏗️ Architecture

LayerService
DataCost Management Query API, grouped by service
AccessCost Management Reader on the subscription
ExplainerFoundry chat model

🛠️ Pull the data

SUB=$(az account show --query id -o tsv)
 
az rest --method post \
  --url "https://management.azure.com/subscriptions/$SUB/providers/Microsoft.CostManagement/query?api-version=2023-11-01" \
  --body '{
    "type": "ActualCost",
    "timeframe": "TheLastMonth",
    "dataset": {
      "granularity": "None",
      "aggregation": { "totalCost": { "name": "Cost", "function": "Sum" } },
      "grouping": [ { "type": "Dimension", "name": "ServiceName" } ]
    }
  }' > last-month.json

💻 Rank in code, explain with the model

import json
 
data = json.load(open("last-month.json"))["properties"]
cols = [c["name"] for c in data["columns"]]
rows = [dict(zip(cols, r)) for r in data["rows"]]
top3 = sorted(rows, key=lambda r: r["Cost"], reverse=True)[:3]
 
prompt = (
    "Explain each of these three Azure charges to a non-technical manager. "
    "For each one say what the service does, why the cost could be this size, "
    "and one thing to check. Do not invent numbers.\n\n" + json.dumps(top3)
)
# send `prompt` with the same client setup as Project 2

🧯 Things that bite

IssueWhat to do
The numbers look slightly staleCost data for the open period refreshes about every four hours
429 responsesThe Query API is rate limited, so cache the result and retry with a backoff
Empty resultCheck you have Cost Management Reader at the scope you queried
Sensitive billGroup by service only. Do not send resource names or IDs to the model

4️⃣ Project 4: An MCP server for a tool you already use

Wrap one tool or API you already use so an AI assistant can call it, and host it as a remote MCP server on Azure Functions.

MCP (Model Context Protocol) is how an assistant like GitHub Copilot calls your tools. A remote server hosted in Azure with Entra sign-in shows you can do the part that matters in a real company: expose something to an AI safely.

🏗️ Architecture

LayerService
ServerAzure Functions with the MCP extension, Flex Consumption plan
AuthBuilt-in MCP authorization with Microsoft Entra, on by default in the template
ClientGitHub Copilot in VS Code, through .vscode/mcp.json

🛠️ Build steps

azd init --template remote-mcp-functions-python -e mcpserver-python
azd up
azd env get-value AZURE_FUNCTION_NAME
  1. Start Azurite and run the project locally with Core Tools. Test it from Copilot using the local-mcp-function entry in .vscode/mcp.json.
  2. Each tool is a function in src/function_app.py marked with the MCP annotation. Copy one of the existing tools and replace the body with a call to the API you already use.
  3. Run azd up, then start the remote-mcp-function entry and sign in with Entra when prompted.
  4. Run azd down when you are done.

The template repo is Azure-Samples/remote-mcp-functions-python. Check its README for the Python version and Core Tools version it needs.

DetailValue
Extension server endpointhttps://<FUNCTION_APP_NAME>.azurewebsites.net/runtime/webhooks/mcp
PlanFlex Consumption, pay for what you use
Typical quickstart costA few US cents or less

🔐 Auth and scope

The template turns on built-in MCP authorization with Entra, so leave it on and sign in when you connect. Then pick what the server is allowed to do.

Once a read-only tool works from Copilot, the project is done. The lab below walks the same pattern from scratch.

Build Interactive MCP Apps with Azure Functions
Hands-on Lab

Build Interactive MCP Apps with Azure Functions

Build and deploy an MCP server on Azure Functions end to end, then point your own tool at it.

cloudlearn.ioStart the lab

5️⃣ Project 5: An invoice reader

Drop invoices into Blob Storage and pull vendor, date, and total out with Document Intelligence.

An event-driven pipeline with a structured output. It is also useful: the same shape handles receipts, forms, and contracts.

🏗️ Architecture

LayerService
InputBlob Storage container invoices
TriggerAzure Functions blob trigger, event-based
ExtractionDocument Intelligence prebuilt-invoice model
OutputA JSON file per invoice in a results container

🛠️ Build steps

  1. Create a storage account with two containers, invoices and results.
  2. Create a Document Intelligence resource. The free tier is enough for testing.
  3. Give the function app's identity Cognitive Services User on the Document Intelligence resource.
  4. Deploy the function, then add an Event Grid event subscription on the storage account that points at the function. The endpoint looks like this, and the blob extension key comes from the function app's system keys:
https://<FUNCTION_APP_NAME>.azurewebsites.net/runtime/webhooks/blobs?functionName=Host.Functions.read_invoice&code=<BLOB_EXTENSION_KEY>
  1. Upload a sample invoice and read the JSON that appears in results.

💻 The function

import json, os
import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.ai.documentintelligence.models import AnalyzeDocumentRequest
 
app = func.FunctionApp()
di = DocumentIntelligenceClient(os.environ["DI_ENDPOINT"], DefaultAzureCredential())
 
FIELDS = ("VendorName", "InvoiceId", "InvoiceDate", "InvoiceTotal")
 
@app.blob_trigger(arg_name="blob", path="invoices/{name}",
                  connection="AzureWebJobsStorage", source="EventGrid")
@app.blob_output(arg_name="out", path="results/{name}.json",
                 connection="AzureWebJobsStorage")
def read_invoice(blob: func.InputStream, out: func.Out[str]):
    poller = di.begin_analyze_document(
        "prebuilt-invoice", AnalyzeDocumentRequest(bytes_source=blob.read())
    )
    result = poller.result()
    if not result.documents:
        out.set(json.dumps({"error": "no invoice found"}))
        return
    fields = result.documents[0].fields
    out.set(json.dumps({
        k: {
            "value": fields[k].get("content") if k in fields else None,
            "confidence": fields[k].get("confidence") if k in fields else None,
        }
        for k in FIELDS
    }))

🧯 Things that bite

IssueWhat to do
Blob trigger never fires on Flex ConsumptionFlex Consumption supports only the event-based blob trigger, which needs source="EventGrid" and an event subscription on the storage account. Container polling is not supported there
Slow trigger on a Consumption plan appPolling triggers can take up to ten minutes to notice a new blob
Authentication fails with the identityUse the endpoint shown on the resource's Keys and Endpoint page, and confirm the role assignment has finished propagating
A wrong total slips throughStore the confidence next to every value and flag anything below your threshold for review
Analyze Forms and Documents with Azure AI Document Intelligence
Hands-on Lab

Analyze Forms and Documents with Azure AI Document Intelligence

Run the prebuilt models and read the output before you build the pipeline around them.

cloudlearn.ioStart the lab

🧹 Cleanup

Delete each project's resource group when you finish. Deleting a resource group removes everything inside it.

az group delete --name rg-docs-chat --yes --no-wait
az group delete --name rg-support-bot --yes --no-wait
az group delete --name rg-bill-explainer --yes --no-wait
az group delete --name rg-invoice-reader --yes --no-wait
azd down   # in the MCP project folder
ProjectWhat keeps billing if you forget
1AI Search on a paid tier, and the model deployments if they are provisioned
2The function app and the storage account
3Almost nothing, which is why it is the safe one to leave running
4The Flex Consumption app and its storage
5The storage account and any paid Document Intelligence tier

📝 What goes in each README

SectionOne line to include
ArchitectureOne diagram with every service and the identity between them
Run itThe exact commands, tested on a clean machine
CostWhat it costs to leave running for a month, and how you tore it down
SecurityHow it authenticates, and which keys you did not use
What brokeThe one error you hit and how you found the cause

What's Next

Bookmark this page

Save it for your next project sprint

Start a project

Apply what you just learned hands-on

Follow on Instagram

Daily cloud tips & behind-the-scenes

Try hands-on labs

Practice in a real cloud environment

Parveen Singh

Parveen Singh

Microsoft Certified Trainer & Cloud Solutions Consultant

Related Field Notes

Found this useful?

Stay in the loop

Weekly cloud insights, no spam

Subscribe

Explore CloudLearn

Hands-on labs & projects

Start Learning

Book Training

Custom cloud training for your team

Get in Touch

On this page

Subscribe