Bài học từ một dự án thật
Cách đây vài tháng tôi ngồi với một team backend .NET đang vận hành chatbot cho hệ thống nội bộ. Họ gọi OpenAI bằng SDK chính hãng, mọi thứ chạy ngon lành cho tới khi khách hàng của họ là một ngân hàng yêu cầu dữ liệu không được rời khỏi hạ tầng on-premise. Team phải chuyển sang một mô hình tự host (Ollama + model mã nguồn mở). Ban đầu họ nghĩ "thay OpenAIClient bằng client khác là xong". Tuần đầu tiên họ phát hiện ra: SDK chỉ là một phần nhỏ. Phần lớn công sức nằm ở cách code đang gọi client.Chat.Completions.CreateAsync(...) được nhúng rải rác khắp 12 service.
Đây không phải câu chuyện để chê ai. Đây là bài học tôi muốn chia sẻ với các bạn đang bắt đầu dự án AI trên .NET: quyết định kiến trúc quan trọng hơn quyết định model.
Chọn SDK quan trọng, chọn abstraction quan trọng hơn.
Bạn chọn GPT-4o-mini hôm nay, nhưng 6 tháng sau có thể phải sang Claude Sonnet, hay xuống llama3.1 on-premise. Nếu code gắn chặt vào một SDK duy nhất, các bạn đang tự chơi khó chính mình.
Hãy cùng mổ xẻ bốn lớp mà bất kỳ tích hợp AI nào trong .NET cũng phải chạm tới.
Lớp 1 – OpenAI SDK chính hãng cho .NET
Đây là entry point phổ biến nhất. Microsoft Learn có document trực tiếp về OpenAI integration trong .NET (learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai), và OpenAI cũng phát hành NuGet package chính hãng: OpenAI (phiên bản ổn định), gần đây thêm OpenAI.Assistants, v.v.
Cách dùng cơ bản:
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
var client = new OpenAIClient(
new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!));
var chatClient = client.GetChatClient("gpt-4o-mini");
var response = await chatClient.CompleteChatAsync(
new UserChatMessage("Giải thích Cartesian Explosion trong GraphQL là gì?"));
Console.WriteLine(response.Value.Content[0].Text);
Điểm mạnh: SDK gọn, type-safe, support streaming, function calling, structured output. Đủ cho 80% use case khi bạn biết chắc sẽ chỉ dùng OpenAI.
Điểm yếu thật: SDK chỉ nói chuyện được với OpenAI API. Nó không biết Azure OpenAI endpoint (URL khác), không biết Anthropic, không biết Ollama. Bạn muốn đổi provider là phải đổi code, đổi NuGet, đổi luôn cách authenticate. Một dự án tôi từng review, team đã bind IChatClient (từ Microsoft.Extensions.AI) trực tiếp vào OpenAIClient – hôm sau PM bảo "thử Claude xem sao", họ phải quét toàn bộ codebase sửa lại. Đỡ hơn rất nhiều nếu từ đầu inject qua interface.
Lớp 2 – Azure.AI.OpenAI: dành riêng cho Azure
Nếu dự án bạn chạy trên Azure, Microsoft đề xuất package Azure.AI.OpenAI (learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.extensions.openai-readme). Khác biệt cốt lõi:
- Endpoint riêng: bạn trỏ tới
https://{resource}.openai.azure.comthay vìhttps://api.openai.com. - Authenticate bằng Azure AD thay vì API key đơn lẻ – phù hợp với hệ thống đã có Azure AD, Entra ID.
- Data residency: model có thể chạy trong region bạn chọn, hỗ trợ compliance.
- Billing gộp vào Azure subscription thay vì quản lý riêng một hoá đơn OpenAI nữa.
using Azure;
using Azure.AI.OpenAI;
var client = new AzureOpenAIClient(
new Uri("https://{your-resource}.openai.azure.com"),
new AzureKeyCredential(credential));
var chat = client.GetChatClient(deploymentName: "gpt-4o-mini-deployment");
Microsoft cũng viết riêng Azure.AI.OpenAI chứ không dùng OpenAI SDK chính hãng, vì Azure cần thêm metadata (deployment name, content filtering, role mapping cho Azure AD). Tuy nhiên surface API rất giống – đây là thiết kế có chủ đích: giúp bạn switch qua lại giữa OpenAI cloud và Azure OpenAI với ít đau đớn nhất.
Lớp 3 – Microsoft.Extensions.AI: abstraction chuẩn
Đây mới là phần tôi muốn các bạn dành thời gian đọc kỹ, vì đây là lớp quyết định bạn có "khóc" khi đổi provider hay không.
Microsoft.Extensions.AI (MEAI) – công bố chính thức 12/2025 theo Microsoft Learn – là một thư viện abstraction trong hệ Microsoft.Extensions.* quen thuộc (giống Microsoft.Extensions.Logging, Microsoft.Extensions.Configuration...). Nó định nghĩa các interface chuẩn: IChatClient, IEmbeddingGenerator, IImageGenerator – và mọi provider cung cấp implementation cho các interface này.
Cài đặt qua NuGet:
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI // adapter cho OpenAI SDK
dotnet add package Microsoft.Extensions.AI.AzureAIInference // adapter cho Azure
dotnet add package Microsoft.Extensions.AI.Ollama // adapter cho Ollama
Ý tưởng cốt lõi: bạn viết code dựa trên IChatClient, application không biết nó đang gọi provider nào. Đổi provider chỉ là đổi implementation trong DI container.
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
// Trong Program.cs
builder.Services.AddChatClient(builder =>
{
builder.UseOpenAI(apiKey, modelName: "gpt-4o-mini");
// Hoặc:
// builder.UseAzureOpenAI(endpoint, deployment, new AzureKeyCredential(key));
// builder.UseOllama("llama3.1");
});
// Ở bất kỳ service nào
public class ChatService(IChatClient chatClient)
{
public async Task<string> AskAsync(string question)
{
var response = await chatClient.CompleteAsync(question);
return response.Message.Text ?? "";
}
}
Bạn thấy điểm hay chưa? ChatService không hề biết nó đang gọi OpenAI, Azure, hay Ollama. Khi PM bảo "thử Claude", tôi chỉ cần thêm một NuGet (khi có adapter chính thức) hoặc custom adapter, sửa một dòng trong Program.cs. Application code không đổi.
Một số bạn hỏi tôi: "Vậy MEAI có phải Semantic Kernel không?" Câu trả lời: không hẳn. Semantic Kernel (SK) là framework AI đầy đủ (planning, function calling orchestration, memory). MEAI chỉ là abstraction layer thuần tuý, nằm bên dưới SK. SK dùng MEAI làm foundation. Nếu bạn chỉ cần gọi chat completion đơn giản, MEAI đủ. Nếu bạn cần agent orchestration, dùng SK (đã build sẵn trên MEAI).
Lớp 4 – Provider không phải OpenAI: OpenAI-compatible endpoint
Đây là lớp quan trọng nhất cho câu chuyện multi-provider. Trong bài viết trên Viblo về OpenAI-compatible APIs, tác giả đã chỉ ra một sự thật thú vị: phần lớn provider mới đều "nói" được ngôn ngữ OpenAI, vì OpenAI đã đặt chuẩn de facto cho ngành.
Bạn có thể trỏ OpenAIClient tới bất kỳ endpoint nào "compatible":
var client = new OpenAIClient(
new ApiKeyCredential("anything"),
new OpenAIClientOptions
{
Endpoint = new Uri("https://api.deepseek.com/v1")
});
var chat = client.GetChatClient("deepseek-chat");
Provider tôi đã dùng thực tế và thấy chạy ổn:
- Anthropic Claude: có Anthropic-compatible proxy (ví dụ
LiteLLM proxy,OpenRouter) expose OpenAI-compatible endpoint. Anthropic chưa ra adapter OpenAI-compatible native, nhưng ecosystem đã tự build. - Google Gemini: cung cấp OpenAI-compatible endpoint tại
https://generativelanguage.googleapis.com/v1beta/openai. - DeepSeek, Qwen, Mistral: tất cả expose OpenAI-compatible REST API. Bạn chỉ cần đổi URL + model name.
- Ollama: chạy local, expose OpenAI-compatible endpoint mặc định ở port 11434. Hoàn hảo cho dev/test.
- Azure OpenAI: cũng OpenAI-compatible (chỉ khác cách authenticate).
Khi bạn kết hợp MEAI + OpenAI-compatible endpoint, bạn có combo cực mạnh: một codebase, một interface, chạy được với 10+ provider.
Kiến trúc tham chiếu cho hệ thống production
Trong những dự án tôi từng tham gia, nhiều team cùng vấp một lỗi: tự wrap OpenAIClient bằng ILLMService ngay từ đầu, viết lại gần như toàn bộ logic. Không cần thiết. MEAI đã là interface đó rồi. Đừng phát minh lại bánh xe.
Kiến trúc tôi recommend cho team bắt đầu:
+--------------------------------------+
| Application code |
| (inject IChatClient) |
+--------------------------------------+
|
v
+--------------------------------------+
| Microsoft.Extensions.AI abstraction |
| IChatClient / IEmbeddingGenerator |
+--------------------------------------+
|
+------------+--------------+
| | |
v v v
OpenAI Azure OpenAI Ollama
(adapter) (adapter) (adapter)
Mỗi adapter chỉ là một class wrap một SDK cụ thể. Khi cần đổi provider, sửa một chỗ trong DI.
Nếu dự án lớn hơn – có nhiều team, cần budget tracking, audit log, routing policy – tôi khuyên thêm một gateway layer trước MEAI. Trong bài viết về LiteLLM trên Viblo, tác giả mô tả chính xác pattern này: gateway đứng giữa application và các provider, xử lý virtual keys, rate limit, cost tracking, fallback. LiteLLM là lựa chọn tốt (open source, 20K+ stars), nhưng nó viết bằng Python. Nếu bạn ở trong hệ .NET toàn tập, có thể tự build gateway bằng ASP.NET Core + MEAI, hoặc dùng commercial gateway hỗ trợ OpenAI-compatible.
Best practices tôi rút ra sau 6 tháng vận hành
1. Inject IChatClient, không inject OpenAIClient. Lý do: provider đổi là chuyện thường, model đổi là chuyện thường, deployment topology đổi cũng là chuyện thường. Interface là điểm neo duy nhất giúp code ổn định.
2. API key đi qua configuration, không hard-code. Dùng IConfiguration + User Secrets (dev) + Azure Key Vault / environment variable (prod). Đừng commit key lên repo, đừng để trong appsettings.json của môi trường thật.
3. Structured logging và telemetry là bắt buộc. Mỗi call tới LLM tốn tiền. Bạn cần log: model nào, bao nhiêu token input/output, latency, status. Nếu không log, hoá đơn cuối tháng sẽ là một cú sốc. Khi dùng MEAI, tôi thường wrap thêm một decorator logging:
builder.Services.AddChatClient(...)
.UseLogging();
4. Retry với exponential backoff cho lỗi 429/503. OpenAI và các provider khác đều có rate limit. Đừng viết retry bằng tay cho từng call site – hãy cấu hình ở HttpClient level hoặc dùng Polly.
5. Không bao giờ trust prompt injection. Dữ liệu từ user (qua prompt) có thể chứa hướng dẫn độc hại ép model leak system prompt hoặc thực hiện action ngoài ý muốn. Validate input ở lớp application, không phải lớp model.
6. Test với deterministic mode khi có thể. Nhiều provider support temperature=0 cho output gần như deterministic – dùng nó trong test. Snapshot test cho output LLM là khó, nhưng test prompt format + tool calling schema thì dễ và nên làm.
Bốn bước bắt đầu ngay hôm nay
Nếu các bạn đang đọc bài này và chưa bắt đầu dự án AI, đây là checklist tôi muốn gửi tới các bạn:
Bước 1: Quyết định provider ban đầu. OpenAI cho nhanh, Azure OpenAI nếu đã ở trên Azure, Ollama nếu cần on-premise. Đừng tốn hai tuần đánh giá 10 provider – chọn một, ship prototype.
Bước 2: Viết code dùng IChatClient từ MEAI ngay từ đầu. Đừng import OpenAI SDK trực tiếp vào business logic.
Bước 3: Thêm logging + cost tracking ở ngày đầu. Đừng đợi tới khi hoá đơn cuối tháng mới "ồ" lên.
Bước 4: Khi provider mới ra (Claude 4.6, GPT-5.1, Gemini 2.5...), bạn chỉ cần viết một adapter mới – không phải đập đi xây lại application.
Câu hỏi tôi hay nhận
"Có nên dùng Semantic Kernel thay vì MEAI?" – SK phù hợp khi bạn cần agent orchestration (nhiều tool, planning, memory). MEAI phù hợp khi bạn chỉ cần gọi chat/embedding đơn giản. SK hiện tại cũng dùng MEAI bên dưới, nên không xung đột. Bắt đầu với MEAI, scale lên SK khi cần.
"Anthropic có adapter MEAI chính thức chưa?" – Tính tới thời điểm tôi viết bài này (2026), Anthropic chưa có adapter chính thức, nhưng có thể dùng OpenAI-compatible endpoint qua proxy (LiteLLM, OpenRouter). Hoặc viết custom IChatClient wrap Anthropic SDK – không khó, khoảng 100 dòng code.
"OpenAI SDK có dùng được cho Azure OpenAI không?" – Không native. Azure cần Azure.AI.OpenAI vì authentication và deployment model khác. Đây là lý do MEAI tồn tại – để bạn không phải quan tâm.
Kết bài
Tôi từng nghĩ chuyện tích hợp LLM vào .NET chỉ là "cài NuGet, gọi API, xong". Hai mươi năm làm nghề dạy tôi rằng mọi thứ tôi nghĩ là "xong" thường chỉ là "bắt đầu". Bài toán thật nằm ở việc thiết kế sao cho hôm nay tôi dùng OpenAI, ngày mai tôi có thể chuyển sang Claude, ngày kia sang một mô hình on-premise mà application vẫn không phải đập đi xây lại.
Tôi chọn nó làm điểm xuất phát, vì gắn code vào một SDK cụ thể cũng giống như khóa database vào một ORM: lúc cần đổi, chi phí sẽ lộ ra.
Gửi các bạn trẻ đang bắt đầu dự án AI đầu tiên: đừng vội tối ưu. Chọn một provider, ship prototype, đo lường chi phí, đo lường latency. Khi có dữ liệu thật, bạn mới biết nên đầu tư vào abstraction ở đâu, fallback ở đâu, gateway ở đâu. Mọi kiến trúc "chuẩn từ đầu" mà chưa có usage thật vẫn chỉ là đoán mò.
Team của các bạn đang khóa vào một provider, hay đã giữ được đường lui khi model thay đổi? Chia sẻ câu chuyện ở comment bên dưới – tôi sẽ đọc và phản hồi từng case.
/Son Do – believe in basic
#1percentbetter #dotnet #ai-architecture #openai #microsoft-extensions-ai