رفتن به محتوا

استفاده از API هوش مصنوعی در C# و .NET با کتابخانهٔ رسمی OpenAI

راهنمای عملی کتابخانهٔ رسمی OpenAI برای .NET با API هیبانا؛ نصب، چت، گفت‌وگوی چندمرحله‌ای، استریم، مدیریت خطا و ASP.NET Core با کد آزموده‌شده.

تیم هیبانا۵ دقیقه مطالعه
رباتی کوچک کنار یک لپ‌تاپ که قطعه‌های شش‌ضلعی بنفش و سبز را روی یک تختهٔ مدار روشن کنار هم می‌چیند و لپ‌تاپ با سیمی به یک ابر وصل است

اگر برنامهٔ شما با C# و .NET نوشته شده، لازم نیست برای اضافه کردن هوش مصنوعی سراغ درخواست‌های HTTP دستی بروید. کتابخانهٔ رسمی OpenAI برای .NET (بستهٔ NuGet با نام OpenAI) امکان تعیین آدرس دلخواه را دارد و چون API هیبانا سازگار با OpenAI است، همین کتابخانه با عوض کردن کلید و آدرس پایه به هیبانا وصل می‌شود. در این مطلب از نصب شروع می‌کنیم و تا پاسخ استریم، مدیریت خطا و یک endpoint در ASP.NET Core پیش می‌رویم. همهٔ نمونه‌ها را با .NET SDK 10.0.402 و نسخهٔ 2.14.0 بستهٔ OpenAI روی API واقعی هیبانا اجرا کرده‌ایم و خروجی‌هایی که می‌بینید واقعی‌اند.

پیش‌نیازها و نصب

به .NET SDK و یک کلید API نیاز دارید. کلید را از داشبورد هیبانا بسازید؛ مراحل تصویری در دریافت کلید API آمده است. طبق README کتابخانه، این کتابخانه با همهٔ برنامه‌های سازگار با .NET Standard 2.0 کار می‌کند، اما نمونه‌های خود README با .NET 10 نوشته شده‌اند و بعضی از آن‌ها به امکانات تازهٔ زبان C# نیاز دارند.

در یک پروژهٔ موجود، بسته را این‌طور اضافه کنید:

dotnet add package OpenAI

برای امتحان سریع، راه کوتاه‌تری هم هست. از .NET 10 به بعد می‌توانید یک فایل .cs را به‌تنهایی و بدون فایل پروژه اجرا کنید؛ مستندات مایکروسافت به این روش «file-based app» می‌گوید. بستهٔ NuGet با دستور #:package در بالای همان فایل معرفی می‌شود و برنامه با dotnet run chat.cs اجرا می‌شود. همهٔ نمونه‌های کنسولی این مطلب همین شکل را دارند. طبق همان مستندات، بدون مدیریت مرکزی بسته‌ها باید نسخه را بعد از @ بنویسید. اگر بعداً پروژهٔ کامل خواستید، dotnet project convert chat.cs از روی همین فایل یک پروژه با فایل .csproj می‌سازد.

کلید را در کد ننویسید

کلید API مثل رمز عبور است و نباید در کد یا مخزن Git قرار بگیرد. نمونه‌های این مطلب آن را از متغیر محیطی HIBANA_API_KEY می‌خوانند. در لینوکس و macOS:

export HIBANA_API_KEY=YOUR_API_KEY

و در PowerShell ویندوز:

$env:HIBANA_API_KEY = "YOUR_API_KEY"

هر دو فقط برای همان پنجرهٔ ترمینال معتبرند. در پروژه‌های ASP.NET Core، که جلوتر به آن می‌رسیم، همین متغیر از طریق Configuration هم خوانده می‌شود.

اولین درخواست چت

کد زیر یک ChatClient می‌سازد که به‌جای سرور OpenAI به هیبانا وصل می‌شود و یک پیام می‌فرستد:

#:package OpenAI@2.14.0 using System.ClientModel;using System.Text.Json;using OpenAI;using OpenAI.Chat; var client = new ChatClient(    model: "gpt-5-nano",    credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("HIBANA_API_KEY")!),    options: new OpenAIClientOptions    {        Endpoint = new Uri("https://api-ai.hibanacloud.ir/v1")    }); List<ChatMessage> messages =[    new SystemChatMessage("پاسخ را کوتاه و به فارسی بده."),    new UserChatMessage("زبان C# در یک جمله چیست؟")]; #pragma warning disable OPENAI001 // ReasoningEffortLevel هنوز در SDK آزمایشی استvar options = new ChatCompletionOptions{    ReasoningEffortLevel = ChatReasoningEffortLevel.Low};#pragma warning restore OPENAI001 ClientResult<ChatCompletion> result = await client.CompleteChatAsync(messages, options);ChatCompletion completion = result.Value; Console.WriteLine(completion.Content[0].Text);Console.WriteLine($"input: {completion.Usage.InputTokenCount}, output: {completion.Usage.OutputTokenCount}"); // cost_rial فیلد اضافهٔ هیبانا است و در کلاس‌های SDK نیست؛ از JSON خام پاسخ خوانده می‌شودusing var json = JsonDocument.Parse(result.GetRawResponse().Content);Console.WriteLine($"cost_rial: {json.RootElement.GetProperty("usage").GetProperty("cost_rial")}");

خروجی واقعی:

C# یک زبان برنامه‌نویسی شیءگرا و سطح‌بالا است که برای توسعه نرم‌افزار با پلتفرم .NET طراحی شده و امکاناتی مانند مدیریت حافظه، همزمانی و کتابخانه‌های گسترده را ارائه می‌دهد.input: 30, output: 130cost_rial: 249

چند نکته در همین کد هست:

  • Endpoint در OpenAIClientOptions تنها تفاوت با کد OpenAI است. README کتابخانه همین روش را برای «API‌های سازگار با OpenAI» پیشنهاد می‌کند. آدرس باید با /v1 تمام شود.
  • model باید دقیقاً همان رشته‌ای باشد که در لیست مدل‌ها آمده است. اینجا از GPT-5 nano، کوچک‌ترین و سریع‌ترین عضو خانوادهٔ GPT-5، استفاده کرده‌ایم؛ قیمت آن در زمان نگارش این مطلب را در صفحهٔ همان مدل ببینید.
  • ReasoningEffortLevel سطح استدلال مدل را پایین می‌آورد و برای کارهای ساده هزینه را کم می‌کند. در نسخهٔ 2.14.0 این گزینه «آزمایشی» علامت خورده و بدون #pragma warning disable OPENAI001 برنامه کامپایل نمی‌شود؛ خطای OPENAI001 را خودمان هنگام آزمون دیدیم.
  • cost_rial هزینهٔ همین درخواست به ریال است. هیبانا این فیلد را به usage اضافه می‌کند، اما کلاس‌های کتابخانه آن را نمی‌شناسند؛ برای همین از GetRawResponse() و JSON خام پاسخ خوانده‌ایم. شمار توکن‌ها در Usage هست. دربارهٔ نحوهٔ محاسبهٔ هزینه، توکن چیست و هزینهٔ API چطور حساب می‌شود را بخوانید.

همین فایل را یک بار هم در PowerShell و با متغیری که به روش بالا تنظیم شده بود اجرا کردیم و بدون تغییر کار کرد.

گفت‌وگوی چندمرحله‌ای

API چیزی از پیام‌های قبلی به خاطر نمی‌سپارد. برای گفت‌وگو، تاریخچه را در یک List<ChatMessage> نگه دارید و پاسخ هر نوبت را با AssistantChatMessage به آن اضافه کنید:

#:package OpenAI@2.14.0 using System.ClientModel;using OpenAI;using OpenAI.Chat; var client = new ChatClient(    model: "gpt-5-nano",    credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("HIBANA_API_KEY")!),    options: new OpenAIClientOptions { Endpoint = new Uri("https://api-ai.hibanacloud.ir/v1") }); #pragma warning disable OPENAI001var options = new ChatCompletionOptions { ReasoningEffortLevel = ChatReasoningEffortLevel.Low };#pragma warning restore OPENAI001 List<ChatMessage> history = [new SystemChatMessage("کوتاه و به فارسی پاسخ بده.")]; foreach (string question in new[] { "فقط یک نام کوتاه برای یک کتاب‌فروشی پیشنهاد بده.", "چرا این نام را انتخاب کردی؟ در یک جمله." }){    history.Add(new UserChatMessage(question));    ChatCompletion completion = await client.CompleteChatAsync(history, options);    history.Add(new AssistantChatMessage(completion)); // پاسخ مدل به تاریخچه اضافه می‌شود    Console.WriteLine($"> {question}\n{completion.Content[0].Text}\n");}

خروجی واقعی:

> فقط یک نام کوتاه برای یک کتاب‌فروشی پیشنهاد بده.کتاب‌سرا > چرا این نام را انتخاب کردی؟ در یک جمله.چون کوتاه، روشن و به‌سادگی قابل تلفظ است و فوراً حس یک کتابفروشی را منتقل می‌کند.

پرسش دوم فقط «این نام» را می‌گوید و مدل آن را می‌فهمد، چون پیام‌های قبلی هم فرستاده شده‌اند. یادتان باشد کل تاریخچه در هر درخواست دوباره فرستاده می‌شود و هزینه‌اش حساب می‌شود؛ در گفت‌وگوهای طولانی، پیام‌های قدیمی را خلاصه یا حذف کنید.

پاسخ استریم؛ نمایش کلمه‌به‌کلمه

برای اینکه کاربر منتظر کل پاسخ نماند، به‌جای CompleteChatAsync از CompleteChatStreamingAsync استفاده کنید. نتیجه را می‌توان با await foreach پیمود:

#:package OpenAI@2.14.0 using System.ClientModel;using OpenAI;using OpenAI.Chat; var client = new ChatClient(    model: "gpt-5-nano",    credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("HIBANA_API_KEY")!),    options: new OpenAIClientOptions    {        Endpoint = new Uri("https://api-ai.hibanacloud.ir/v1")    }); #pragma warning disable OPENAI001var options = new ChatCompletionOptions { ReasoningEffortLevel = ChatReasoningEffortLevel.Low };#pragma warning restore OPENAI001 AsyncCollectionResult<StreamingChatCompletionUpdate> updates = client.CompleteChatStreamingAsync(    [new UserChatMessage("در دو جملهٔ کوتاه بگو async/await در C# چه کمکی می‌کند.")],    options); await foreach (StreamingChatCompletionUpdate update in updates){    foreach (ChatMessageContentPart part in update.ContentUpdate)    {        Console.Write(part.Text);    }     if (update.Usage is not null)    {        Console.WriteLine($"\n\ninput: {update.Usage.InputTokenCount}, output: {update.Usage.OutputTokenCount}");    }}

خروجی واقعی:

async/await کار با عملیات غیرهمزمان را به صورت کد همزمان اما غیر بلاک‌کننده ساده می‌کند و از مسدود شدن thread اصلی جلوگیری می‌کند. با استفاده از Taskها مدیریت ادامه‌ی اجرا به‌سادگی انجام می‌شود و کد روشن‌تر و قابل نگهداری‌تر می‌شود. input: 26, output: 204

هر به‌روزرسانی فقط تکهٔ تازهٔ متن را در ContentUpdate دارد و ممکن است خالی باشد. برخلاف کتابخانهٔ Node.js، اینجا لازم نیست مصرف توکن را جداگانه درخواست کنید: درخواستی را که کتابخانه می‌فرستد بررسی کردیم و stream_options: {"include_usage": true} را خودش اضافه کرده بود و آخرین به‌روزرسانی Usage را داشت. جزئیات قالب تکه‌ها در مستندات استریم آمده است.

مدیریت خطا، تلاش مجدد و timeout

وقتی سرور کد خطا برمی‌گرداند، کتابخانه یک ClientResultException پرتاب می‌کند. کد HTTP در Status است و بدنهٔ خطا را می‌توان از GetRawResponse() خواند. طبق README کتابخانه، خطاهای ۴۰۸، ۴۲۹، ۵۰۰، ۵۰۲، ۵۰۳ و ۵۰۴ به‌طور پیش‌فرض تا سه بار دیگر با فاصلهٔ فزاینده دوباره امتحان می‌شوند. تعداد تلاش‌ها و حداکثر زمان هر درخواست را می‌توانید هنگام ساختن کلاینت تعیین کنید:

#:package OpenAI@2.14.0 using System.ClientModel;using System.ClientModel.Primitives;using System.Text.Json;using OpenAI;using OpenAI.Chat; string model = args.Length > 0 ? args[0] : "gpt-5-nano"; var client = new ChatClient(    model: model,    credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("HIBANA_API_KEY")!),    options: new OpenAIClientOptions    {        Endpoint = new Uri("https://api-ai.hibanacloud.ir/v1"),        RetryPolicy = new ClientRetryPolicy(maxRetries: 2), // پیش‌فرض ۳ تلاش اضافه است        NetworkTimeout = TimeSpan.FromSeconds(60)    }); try{    ChatCompletion completion = await client.CompleteChatAsync("فقط بنویس: سلام");    Console.WriteLine($"پاسخ: {completion.Content[0].Text}");}catch (ClientResultException ex){    // بدنهٔ خطای هیبانا: {"error": {"message": "...", "code": "..."}}    string? code = null, message = null;    string? body = ex.GetRawResponse()?.Content.ToString();    if (!string.IsNullOrEmpty(body))    {        using var json = JsonDocument.Parse(body);        if (json.RootElement.TryGetProperty("error", out var error))        {            code = error.TryGetProperty("code", out var c) ? c.ToString() : null;            message = error.TryGetProperty("message", out var m) ? m.GetString() : null;        }    }     string hint = ex.Status switch    {        400 => "درخواست یا نام مدل را بررسی کنید",        401 => "کلید یا هدر Authorization را بررسی کنید",        402 => "موجودی کیف پول یا سقف بودجهٔ کلید",        429 => "کمی صبر کنید و دوباره بفرستید",        _ => "خطای سرور؛ بعداً دوباره امتحان کنید"    };    Console.WriteLine($"{ex.Status}: {code} → {hint}");    if (message is not null) Console.WriteLine($"   {message}");}

این برنامه را سه بار اجرا کردیم: یک بار عادی، یک بار با نام مدلی که وجود ندارد (dotnet run errors.cs -- gpt-unknown) و یک بار با کلید غلط در HIBANA_API_KEY. خروجی واقعی به همان ترتیب:

پاسخ: سلام 400: model_not_found → درخواست یا نام مدل را بررسی کنید   The requested model 'gpt-unknown' was not found or is disabled. 401: invalid_api_key → کلید یا هدر Authorization را بررسی کنید   Invalid API key provided. Pass your key as 'Authorization: Bearer <key>' or 'X-Api-Key: <key>'.

برنامه باید روی code تصمیم بگیرد، نه روی متن پیام؛ code شناسهٔ پایدار خطاست. ۴۰۰ و ۴۰۱ دوباره امتحان نمی‌شوند، چون تکرار آن‌ها نتیجه را عوض نمی‌کند. معنی هر کد خطا و راه‌حل آن را در خطاهای API هوش مصنوعی مفصل نوشته‌ایم و فهرست کامل کدها در مستندات خطاها آمده است.

کلید فقط روی سرور

کلید API را هرگز در برنامه‌ای که به دست کاربر می‌رسد، مثل یک برنامهٔ دسکتاپ، موبایل یا Blazor WebAssembly، قرار ندهید؛ هر کسی می‌تواند آن را از فایل برنامه بیرون بکشد. درخواست را از سرور خودتان بفرستید و فقط پاسخ را به کاربر برگردانید؛ بخش بعد همین کار را نشان می‌دهد.

استفاده در ASP.NET Core

طبق README، کلاینت‌های این کتابخانه thread-safe هستند و می‌توان آن‌ها را به‌صورت singleton در DI ثبت کرد تا اتصال‌های HTTP دوباره استفاده شوند. برای این بخش یک پروژهٔ وب معمولی ساختیم:

dotnet new web -n HibanaApicd HibanaApidotnet add package OpenAI

و محتوای Program.cs را با این کد عوض کردیم:

 using System.ClientModel;using OpenAI;using OpenAI.Chat; var builder = WebApplication.CreateBuilder(args); // ChatClient را یک بار بسازید و همه‌جا از همان استفاده کنیدbuilder.Services.AddSingleton(new ChatClient(    model: "gpt-5-nano",    credential: new ApiKeyCredential(builder.Configuration["HIBANA_API_KEY"]!),    options: new OpenAIClientOptions { Endpoint = new Uri("https://api-ai.hibanacloud.ir/v1") })); var app = builder.Build(); app.MapPost("/ask", async (AskRequest request, ChatClient chat) =>{    ChatCompletion completion = await chat.CompleteChatAsync(        new SystemChatMessage("کوتاه و به فارسی پاسخ بده."),        new UserChatMessage(request.Question));    return Results.Ok(new { answer = completion.Content[0].Text });}); app.Run(); record AskRequest(string Question);

builder.Configuration در ASP.NET Core متغیرهای محیطی را هم می‌خواند، پس همان HIBANA_API_KEY کافی است. برنامه را با dotnet run --urls http://localhost:5081 اجرا کردیم و با curl یک درخواست فرستادیم:

curl -X POST http://localhost:5081/ask \  -H "Content-Type: application/json" \  -d '{"question":"ASP.NET Core در یک جمله چیست؟"}'

پاسخ واقعی:

{"answer":"ASP.NET Core یک فریمورک متن‌باز و کراس‌پلتفرم با کارایی بالا است که برای ساخت برنامه‌های وب، وب API و میکروسرویس‌ها با استفاده از پلتفرم .NET طراحی شده است."}

یک تجربهٔ ناموفق هم داشتیم که دانستنش می‌تواند در وقت شما صرفه‌جویی کند: همین کد را اول به شکل file-based app نوشتیم و endpoint با خطای ۵۰۰ و NotSupportedException برای AskRequest شکست خورد. طبق مستندات مایکروسافت، file-based app‌ها به‌طور پیش‌فرض برای Native AOT تنظیم شده‌اند و در این حالت تبدیل خودکار JSON به کلاس‌های خودتان کار نمی‌کند. با افزودن خط #:property PublishAot=false به بالای فایل، همان کد درست پاسخ داد. بااین‌حال برای برنامهٔ وب، پروژهٔ معمولی انتخاب ساده‌تری است.

جمع‌بندی

برای اتصال C# و .NET به هیبانا کافی است بستهٔ OpenAI را اضافه کنید، کلید را در متغیر محیطی بگذارید و Endpoint را روی https://api-ai.hibanacloud.ir/v1 تنظیم کنید. از آنجا به بعد همه‌چیز همان کتابخانهٔ آشناست: CompleteChatAsync برای چت، CompleteChatStreamingAsync برای پاسخ زنده و ClientResultException برای خطاها. نمونه‌کد زبان‌های دیگر در نمونه‌کدها و شروع سریع آمده و اگر با جاوااسکریپت هم کار می‌کنید، اتصال Node.js به API هوش مصنوعی همین مسیر را برای Node.js طی می‌کند.

مطالب مرتبط

با یک کلید به همه مدل‌ها وصل شوید

GPT، Claude، Gemini و ده‌ها مدل دیگر، با پرداخت ریالی و یک API سازگار با OpenAI.