Microsoft Teams · Bot Framework · Azure OpenAI · Microsoft Graph

IT Support
Copilot

I built a personal prototype that checks whether an IT request can be fulfilled, needs approval, or should be declined. It is separate from my employer work.

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

What it is

I built a Teams-based assistant with four tools for IT requests. The AI interprets the request; separate rules decide what happens next. The demo uses sample data by default. Connecting it to live Microsoft Graph requires configuration.

What I explored

I explored how clear rules and human approvals can guide an IT assistant.

Skills and tools

  • 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.

I kept interpretation separate from permission. The AI identifies what the person wants. Fixed rules decide whether the request can proceed or needs approval.

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.