Microsoft Teams · Bot Framework · Azure OpenAI · Microsoft Graph

IT Support
Copilot

A Teams-deployed copilot that handles internal IT requests end-to-end — password resets, VPN access, group memberships, laptop tickets. Azure OpenAI for reasoning, Microsoft Graph for identity, a deterministic policy engine for the decision. Self-serve, auto-fulfill, escalate, or deny — every outcome auditable.

Bot Framework · Teams Azure OpenAI · Graph SDK Mock + live modes 4 tools · 4 outcomes
At a glance 30-second read

What it is

A complete internal-IT copilot stack — Teams Bot Framework wraps a FastAPI endpoint that calls Azure OpenAI with four Microsoft-Graph-backed tools. The LLM picks tools; a separate rule-based policy engine decides what happens. Runs offline against fixture data by default so the demo always works; GRAPH_MODE=live swaps in real Microsoft Graph via managed identity.

Maps to

AI Transformation Lead · Microsoft Copilot Engineer · Enterprise AI Architect · Identity Engineer (Microsoft)

Skills demonstrated

  • Bot Framework v4 adapter + Teams app manifest
  • Microsoft Graph SDK (User, Group, GroupMember APIs)
  • Azure OpenAI with function calling — gpt-4o-mini
  • Deterministic policy engine separated from the LLM
  • Bicep IaC sketch for Container Apps + Bot Service
  • Managed identity for Graph + Azure OpenAI auth
  • End-to-end audit trail for every tool call + decision
Bot Framework Microsoft Graph Azure OpenAI Entra ID FastAPI Bicep Python
01 — Four Outcomes one decision per turn

Every request routes to one of four answers.

The split between LLM reasoning (what the user is asking for) and rule-based policy (what happens next) is deliberate. The LLM is good at understanding language; rules are good at being audit-defensible.

SELF_SERVE
Reply with KB
A KB article fully answers the question. Copilot sends the summary inline plus a link to the full article.
e.g. "How do I reset my password?"
AUTO_FULFILL
Make the change
User is eligible without approval. Copilot calls Graph to make the change and reports back. Audit log records who/what/when.
e.g. "I need VPN access"
ESCALATE
Route to approver
Approval required. Copilot opens the approval request to the named role with full context — user, justification, applicable KB.
e.g. "Prod deploy access"
DENY
Hard stop
A rule prevents the request — disabled account, wrong department, missing prerequisite. Copilot explains why; no ticket opened.
e.g. Sales rep asking for Finance · SOX
02 — A Real Conversation replayed from the committed transcript

Walk-through.

Alex Thompson (Software Engineer I) asks for production deploy access. The copilot identifies him, checks the group, sees the elevated-approval policy, and routes the request to the right approver with the right context. One turn.

Teams DM · IT Support Copilot [ TRANSCRIPT ]
alex.thompson@contoso.com · 10:14 AM
I need access to production deploy so I can ship the new build
IT Support Copilot · 10:14 AM
This needs engineering-vp approval — I've routed the request. Reason: Group g-prod-deploy policy 'elevated-approval' requires engineering-vp approval. ESCALATE
→ lookup_user(user_principal_name='alex.thompson@contoso.com')
→ check_group_access(user_id='u-007-e52d', group_id='g-prod-deploy')
→ policy = escalate → engineering-vp
03 — The Split where the LLM stops and the rules start

LLM understands. Rules decide.

The agent's reasoning is the LLM. The access decision is not. Each layer does what it's best at — and the policy engine is what makes the system audit-defensible against a security review.

LLM Layer (Azure OpenAI)
Understand & route

Reads the user's message in plain language, picks the right tool, refines arguments, asks clarifying questions if needed, drafts the final reply. What the LLM never does: approve access, override policy, or short-circuit the gate. Even if a prompt-injection attempt smuggles instructions, the runtime won't call request_access without a matching policy decision.

Policy Engine (deterministic)
Decide & explain

A pure-Python function with a documented decision table. Same input → same output → same trail of reasoning that ends up in the audit log. What the policy never does: understand natural language. It operates on resolved data — a user record, a group with its policy field, an optional KB article — and returns one of four outcomes plus a reason.

04 — Four Tools three read · one write

What the LLM can call.

Tools are intentionally narrow. Each one does exactly one thing, returns a JSON-shaped result, and is composable with the others.

ToolPurposeSide effects
lookup_userResolve a UPN to a user record — department, manager, group memberships, account status. Always the first call.None
check_group_accessRead the group's policy (auto-on-join, manager-approval, compliance-approval, elevated-approval) and whether the user is already a member.None
search_kbToken-set scoring against title + tags + summary. Returns top articles. Used to answer self-service questions before considering a group change.None
request_accessPOST to Microsoft Graph's /groups/{id}/members/$ref. Only callable after the policy engine has returned auto_fulfill.Mutates Graph
05 — Try it offline demo · no Azure required

Clone, run, chat.

The default mode is offline — deterministic router, fixture data, no API keys. Switch to Azure OpenAI + live Microsoft Graph with two env vars.

offline · fixture data [ FAST PATH ]
$ git clone https://github.com/iambrucedavis/it-support-copilot
$ cd it-support-copilot
$ python3 -m venv .venv && source .venv/bin/activate
$ pip install -r requirements.txt

# REPL mode — pick a user, ask questions
$ python scripts/demo_chat.py --user bryan.jenkins@contoso.com

# Replay a committed transcript
$ python scripts/demo_chat.py --replay examples/conversations/vpn-self-serve.transcript.json
live · Azure OpenAI + Microsoft Graph [ PRODUCTION PATH ]
$ export GRAPH_MODE=live
$ export COPILOT_MODE=azure
$ export AZURE_OPENAI_ENDPOINT=https://your-aoi.openai.azure.com
$ export AZURE_OPENAI_API_KEY=...
$ az login                                                # or service principal env vars

$ uvicorn copilot_agent.server:app --port 8000           # /api/messages = Bot Framework webhook

Full Teams deployment (Bot Service registration, Container Apps, managed identity, Bicep templates) is documented in deploy/README.md. Sized for a low-volume internal pilot, scales to thousands of users by adjusting Container App replicas.