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.
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.
AI Transformation Lead · Microsoft Copilot Engineer · Enterprise AI Architect · Identity Engineer (Microsoft)
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.
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.
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.
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.
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.
Tools are intentionally narrow. Each one does exactly one thing, returns a JSON-shaped result, and is composable with the others.
| Tool | Purpose | Side effects |
|---|---|---|
| lookup_user | Resolve a UPN to a user record — department, manager, group memberships, account status. Always the first call. | None |
| check_group_access | Read the group's policy (auto-on-join, manager-approval, compliance-approval, elevated-approval) and whether the user is already a member. | None |
| search_kb | Token-set scoring against title + tags + summary. Returns top articles. Used to answer self-service questions before considering a group change. | None |
| request_access | POST to Microsoft Graph's /groups/{id}/members/$ref. Only callable after the policy engine has returned auto_fulfill. | Mutates Graph |
The default mode is offline — deterministic router, fixture data, no API keys. Switch to Azure OpenAI + live Microsoft Graph with two env vars.
$ 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
$ 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.