GitHub Copilot chat feels like magic, but under the hood it's three things: a system prompt, context (your code, your repo), and a model. You can build the same stack yourself on Azure OpenAI — with your own data, your own guardrails, and billing under your control. Here's the whole thing in .NET.
Step 1 — Azure OpenAI resource + deployment
In the Azure Portal: create an Azure OpenAI resource, then in Azure AI Foundry deploy a model (e.g. gpt-4o-mini for cheap dev work, gpt-4o/gpt-5-class for harder reasoning). Two values matter:
Endpoint: https://<your-resource>.openai.azure.com/
API key: found under "Keys and Endpoint"
Deployment name: the name YOU gave the deployment (e.g. "gpt-4o-mini")
🔒 DevKing rule: the API key goes in
AZURE_OPENAI_API_KEYenv var — never in source, never in git.
Step 2 — The chat loop (Azure.AI.OpenAI SDK)
dotnet add package Azure.AI.OpenAI
The official SDK now wraps the OpenAI .NET client, so the code is pleasantly short:
using Azure;
using Azure.AI.OpenAI;
using OpenAI.Chat;
var client = new AzureOpenAIClient(
new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
new ApiKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!));
// ChatClient is keyed by your DEPLOYMENT name
ChatClient chat = client.GetChatClient("gpt-4o-mini");
var messages = new List<ChatMessage>
{
new SystemChatMessage("You are a senior .NET pair programmer. Answer with code when possible. Be concise."),
};
while (true)
{
Console.Write("> ");
messages.Add(new UserChatMessage(Console.ReadLine()));
var response = await chat.CompleteChatAsync(messages);
var reply = response.Value.Content[0].Text;
Console.WriteLine(reply);
messages.Add(new AssistantChatMessage(reply));
}
That SystemChatMessage is your Copilot personality. Everything Copilot chat does, you now own.
Step 3 — What makes it "Copilot-like"
A raw chat loop answers generic questions. Copilot's edge is context injection:
- Relevant files — pull the file the user has open into the system prompt or a user message. Even naive "here's the current file" pasting beats zero context.
- Retrieval (RAG) — embed your repo with Azure AI Search or a local vector store; stuff the top-k snippets in before the question.
- Tool calling — register C# methods (build, run tests, search docs) as function tools so the model can act, not just answer.
var options = new ChatCompletionOptions();
options.Tools.Add(ChatTool.CreateFunctionTool(
functionName: "run_dotnet_test",
functionDescription: "Run dotnet test on the current project",
functionParameters: BinaryData.FromString("""{"type":"object","properties":{}}""")));
The model decides when to call it; you execute and feed the result back as a ToolChatMessage. That loop is an agent.
Cost & guardrails (the part nobody demos)
- gpt-4o-mini is fine for dev/test — roughly cents per long session. Swap deployments without changing code.
- Azure OpenAI adds content filters by default — tune them per-deployment in Foundry.
- Set a rate limit (TPM) on the deployment so a runaway loop can't burn your budget.
- Data sent to Azure OpenAI is not used for training — that's the enterprise pitch vs. consumer chatbots.
Checklist
- [ ] Azure OpenAI resource + model deployment (Foundry)
- [ ] Endpoint + key in environment variables
- [ ] Working chat loop with a real system prompt
- [ ] One context-injection trick (open file, RAG, or a tool)
Next level: wire the same client into a minimal VS Code / CLI front-end and you have a private Copilot for your team.
Connect
If this kind of post is useful, the easiest way to support the work is to:
- Star / follow on dev.to (you're already here 🙂)
- Follow on X: @devkingov
- Reach out for HK-based dev work — .NET / Azure / system integration / IT security: studio.resurrects.co or email devkingov@gmail.com
- Subscribe to weekly HK tech posts → studio.resurrects.co/blog (one email a week, no spam)
Top comments (1)
The "three things" reduction is honest, and most teams skip it until their assistant feels magical in week one and useless in week three. The part that decides that curve is context freshness.
How do you handle staleness — re-index the repo on every push, or lazy at conversation start? That choice usually ends up more important than the system prompt itself.