هوش مصنوعی با C# و ASP.NET Core؛ آموزش ساخت API، چت‌بات و سرویس AI

در این آموزش عملی یاد می‌گیرید با C# و ASP.NET Core یک سرویس هوش مصنوعی بسازید، آن را به API درواره متصل کنید و پاسخ معمولی، Streaming و JSON ساختاریافته دریافت کنید.

Share
هوش مصنوعی با C# و ASP.NET Core؛ آموزش ساخت API، چت‌بات و سرویس AI

برای اضافه‌کردن هوش مصنوعی به یک نرم‌افزار سازمانی، وب‌سایت، پنل مدیریتی، فروشگاه اینترنتی یا اپلیکیشن تحت وب، لازم نیست کل معماری پروژه را تغییر دهید. اگر Backend شما با C# و ASP.NET Core نوشته شده است، می‌توانید از طریق یک API استاندارد قابلیت‌هایی مانند چت‌بات، تولید محتوا، خلاصه‌سازی، طبقه‌بندی متن، استخراج اطلاعات و تحلیل داده را به نرم‌افزار اضافه کنید.

در این آموزش، یک پروژه واقعی ASP.NET Core Web API می‌سازیم و آن را به API هوش مصنوعی درواره متصل می‌کنیم. پروژه نهایی فقط یک نمونه ساده ارسال درخواست نیست؛ بلکه ساختاری قابل‌توسعه برای استفاده در محصولات واقعی خواهد داشت.

در پایان مقاله می‌توانید:

  • از C# به مدل‌های هوش مصنوعی درخواست ارسال کنید.
  • API Key را خارج از کد منبع نگه دارید.
  • یک Endpoint چت در ASP.NET Core بسازید.
  • پاسخ مدل را به‌صورت عادی یا Streaming دریافت کنید.
  • خروجی JSON ساختاریافته تولید کنید.
  • خطاهای سرویس بالادستی را مدیریت کنید.
  • مصرف توکن و هزینه درخواست‌ها را کنترل کنید.
  • پروژه را برای استفاده در محیط Production آماده کنید.

چرا C# و ASP.NET Core برای ساخت برنامه هوش مصنوعی مناسب‌اند؟

بخش زیادی از آموزش‌های هوش مصنوعی با پایتون (Python) یا جاوااسکریپت (JavaScript) نوشته می‌شوند؛ اما C# نیز انتخاب بسیار مناسبی برای ساخت محصولات مبتنی بر هوش مصنوعی است، به‌ویژه زمانی که نرم‌افزار اصلی شما بر پایه فناوری‌های مایکروسافت توسعه یافته باشد.

مهم‌ترین مزایای این ترکیب عبارت‌اند از:

  • عملکرد مناسب و پشتیبانی کامل از پردازش ناهمگام
  • معماری استاندارد Dependency Injection
  • امکان ساخت Web APIهای مقیاس‌پذیر
  • پشتیبانی داخلی از JSON با System.Text.Json
  • ابزارهای مناسب برای Logging و Configuration
  • پشتیبانی از Streaming
  • مناسب برای معماری Microservice
  • امکان استقرار روی Linux، Windows، Docker و سرویس‌های ابری
  • یکپارچگی ساده با SQL Server، PostgreSQL، Redis و پیام‌رسان‌ها
  • جامعه توسعه‌دهندگان بزرگ در نرم‌افزارهای سازمانی

ASP.NET Core از هر دو روش Controllers و Minimal APIs برای ساخت Web API پشتیبانی می‌کند. در این آموزش از Controller استفاده می‌کنیم تا ساختار پروژه برای توسعه قابلیت‌های بیشتر مرتب‌تر باشد. این الگو در مستندات رسمی ASP.NET Core Web API نیز توضیح داده شده است.

چه برنامه‌هایی می‌توان با C# و هوش مصنوعی ساخت؟

ترکیب ASP.NET Core و API هوش مصنوعی در پروژه‌های متنوعی کاربرد دارد:

کاربردنمونه قابلیت
چت‌بات پشتیبانیپاسخ به پرسش‌های متداول کاربران
دستیار سازمانیجست‌وجو و پاسخ‌گویی روی اسناد داخلی
فروشگاه اینترنتیتولید توضیحات محصول و مقایسه کالاها
نرم‌افزار CRMخلاصه‌سازی مکالمات و پیشنهاد پاسخ
سامانه آموزشیتولید تمرین و توضیح مفاهیم
ابزار تولید محتوانوشتن پیش‌نویس مقاله، ایمیل و کپشن
تحلیل بازخوردتشخیص موضوع و احساس کلی نظرات
پردازش اسناداستخراج اطلاعات از متن و تبدیل آن به JSON
ابزار برنامه‌نویسیتوضیح، بازنویسی و مستندسازی کد
داشبورد مدیریتیتبدیل داده‌ها به خلاصه قابل‌فهم

در یک محصول واقعی، رابط کاربری مستقیماً نباید به API مدل متصل شود. درخواست باید ابتدا به Backend شما برسد و سپس ASP.NET Core آن را به API درواره ارسال کند.

معماری پیشنهادی پروژه

جریان درخواست در این آموزش به این شکل است:

  1. کاربر پیام را در وب‌سایت یا اپلیکیشن وارد می‌کند.
  2. Frontend پیام را به ASP.NET Core Web API می‌فرستد.
  3. Backend درخواست را اعتبارسنجی می‌کند.
  4. Backend با API Key محرمانه به درواره متصل می‌شود.
  5. درواره درخواست را به مدل انتخابی ارسال می‌کند.
  6. پاسخ مدل به Backend برمی‌گردد.
  7. Backend پاسخ کنترل‌شده را به Frontend تحویل می‌دهد.

مزیت این معماری آن است که API Key در مرورگر، فایل JavaScript یا اپلیکیشن منتشرشده قرار نمی‌گیرد. همچنین می‌توانید احراز هویت، محدودیت مصرف، ثبت لاگ، کش و کنترل هزینه را در Backend پیاده‌سازی کنید.

پیش‌نیازهای آموزش

برای انجام این پروژه به موارد زیر نیاز دارید:

  • یک نسخه پشتیبانی‌شده از .NET SDK
  • Visual Studio، Visual Studio Code یا JetBrains Rider
  • آشنایی مقدماتی با C# و ASP.NET Core
  • حساب کاربری در درواره
  • API Key درواره
  • یک مدل متنی مناسب

برای مشاهده مدل‌های قابل‌استفاده و هزینه آن‌ها به صفحه مدل‌های درواره مراجعه کنید.

در نمونه‌کدها از مقادیر زیر استفاده می‌کنیم:

Base URL:
https://api.darvareh.ir/v1

API Key:
YOUR_DARVAREH_API_KEY

Model ID:
YOUR_MODEL_ID

مقدار YOUR_MODEL_ID را با شناسه مدلی که در درواره انتخاب کرده‌اید جایگزین کنید.

ساخت پروژه ASP.NET Core Web API

ترمینال را باز کنید و دستور زیر را اجرا کنید:

dotnet new webapi -n DarvarehAiApi
cd DarvarehAiApi

سپس پروژه را اجرا کنید:

dotnet run

آدرس دقیق اجرای پروژه در خروجی ترمینال نمایش داده می‌شود. معمولاً یکی از آدرس‌های زیر خواهد بود:

http://localhost:5000
https://localhost:7000

اگر پروژه بدون خطا اجرا شد، آن را متوقف کنید و ساختار برنامه را تکمیل کنید.

ساختار پیشنهادی پوشه‌ها

برای جلوگیری از پراکندگی کد، ساختار زیر را در نظر می‌گیریم:

DarvarehAiApi/
├── Controllers/
│   └── AiController.cs
├── Models/
│   ├── AiApiModels.cs
│   └── ChatModels.cs
├── Options/
│   └── DarvarehOptions.cs
├── Services/
│   └── DarvarehAiClient.cs
├── appsettings.json
└── Program.cs

در پروژه‌های بزرگ‌تر می‌توانید لایه‌های Application، Domain و Infrastructure را نیز جدا کنید؛ اما برای یک سرویس کوچک یا متوسط، ساختار بالا کافی و قابل‌نگهداری است.

تعریف تنظیمات اتصال به درواره

فایل Options/DarvarehOptions.cs را بسازید:

namespace DarvarehAiApi.Options;

public sealed class DarvarehOptions
{
    public const string SectionName = "Darvareh";

    public string BaseUrl { get; init; } = "https://api.darvareh.ir/v1";
    public string ApiKey { get; init; } = string.Empty;
    public string Model { get; init; } = string.Empty;
}

سپس بخش زیر را به appsettings.json اضافه کنید:

{
  "Darvareh": {
    "BaseUrl": "https://api.darvareh.ir/v1",
    "ApiKey": "",
    "Model": "YOUR_MODEL_ID"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

API Key واقعی را در appsettings.json قرار ندهید؛ مخصوصاً اگر پروژه در Git نگهداری می‌شود.

نگهداری امن API Key در محیط توسعه

برای محیط توسعه می‌توانید از Secret Manager دات‌نت استفاده کنید:

dotnet user-secrets init

سپس API Key را ثبت کنید:

dotnet user-secrets set "Darvareh:ApiKey" "YOUR_DARVAREH_API_KEY"

شناسه مدل را نیز می‌توانید به همین روش ذخیره کنید:

dotnet user-secrets set "Darvareh:Model" "YOUR_MODEL_ID"

مایکروسافت توصیه می‌کند اطلاعات حساس در کد منبع یا فایل‌های Configuration قابل‌انتشار قرار نگیرند. Secret Manager برای توسعه محلی مناسب است، اما یک مخزن رمزنگاری‌شده مخصوص Production محسوب نمی‌شود. جزئیات بیشتر در مستندات رسمی مدیریت Secrets در ASP.NET Core ارائه شده است.

در محیط Production می‌توانید از Secret Store زیرساخت، Environment Variable یا سرویس مدیریت اسرار استفاده کنید.

نمونه متغیر محیطی در Linux:

export Darvareh__ApiKey="YOUR_DARVAREH_API_KEY"
export Darvareh__Model="YOUR_MODEL_ID"

در نام متغیرهای محیطی ASP.NET Core، دو علامت زیرخط __ معادل جداکننده : در Configuration است.

تعریف مدل ورودی چت

فایل Models/ChatModels.cs را بسازید:

using System.ComponentModel.DataAnnotations;

namespace DarvarehAiApi.Models;

public sealed class ChatRequest
{
    [Required]
    [StringLength(12000, MinimumLength = 1)]
    public string Message { get; init; } = string.Empty;

    [StringLength(2000)]
    public string? SystemPrompt { get; init; }
}

public sealed record ChatResponse(
    string Answer,
    string Model,
    TokenUsage? Usage
);

public sealed record TokenUsage(
    int PromptTokens,
    int CompletionTokens,
    int TotalTokens
);

ویژگی‌های Required و StringLength باعث می‌شوند درخواست‌های خالی یا بیش‌ازحد طولانی پیش از ارسال به مدل رد شوند.

محدودیت 12000 تنها یک مقدار نمونه برای برنامه ما است و ارتباط مستقیمی با Context Window مدل ندارد. باید آن را بر اساس نوع محصول، مدل انتخابی، هزینه و تجربه کاربری تنظیم کنید.

تعریف مدل پاسخ API هوش مصنوعی

فایل Models/AiApiModels.cs را ایجاد کنید:

using System.Text.Json.Serialization;

namespace DarvarehAiApi.Models;

public sealed class AiCompletionResponse
{
    [JsonPropertyName("model")]
    public string Model { get; init; } = string.Empty;

    [JsonPropertyName("choices")]
    public List<AiChoice> Choices { get; init; } = [];

    [JsonPropertyName("usage")]
    public AiUsage? Usage { get; init; }
}

public sealed class AiChoice
{
    [JsonPropertyName("message")]
    public AiMessage Message { get; init; } = new();
}

public sealed class AiMessage
{
    [JsonPropertyName("role")]
    public string Role { get; init; } = string.Empty;

    [JsonPropertyName("content")]
    public string Content { get; init; } = string.Empty;
}

public sealed class AiUsage
{
    [JsonPropertyName("prompt_tokens")]
    public int PromptTokens { get; init; }

    [JsonPropertyName("completion_tokens")]
    public int CompletionTokens { get; init; }

    [JsonPropertyName("total_tokens")]
    public int TotalTokens { get; init; }
}

این کلاس‌ها فقط بخش‌هایی از پاسخ را مدل‌سازی می‌کنند که در برنامه لازم داریم. لازم نیست تمام فیلدهای احتمالی پاسخ در کلاس C# تعریف شوند.

ساخت سرویس اتصال به API درواره

فایل Services/DarvarehAiClient.cs را بسازید:

using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using DarvarehAiApi.Models;
using DarvarehAiApi.Options;
using Microsoft.Extensions.Options;

namespace DarvarehAiApi.Services;

public sealed class DarvarehAiClient
{
    private readonly HttpClient _httpClient;
    private readonly DarvarehOptions _options;
    private readonly ILogger<DarvarehAiClient> _logger;

    public DarvarehAiClient(
        HttpClient httpClient,
        IOptions<DarvarehOptions> options,
        ILogger<DarvarehAiClient> logger)
    {
        _httpClient = httpClient;
        _options = options.Value;
        _logger = logger;
    }

    public async Task<ChatResponse> CompleteAsync(
        string message,
        string? systemPrompt,
        CancellationToken cancellationToken)
    {
        var payload = new
        {
            model = _options.Model,
            messages = BuildMessages(message, systemPrompt),
            temperature = 0.3,
            max_tokens = 1000
        };

        using var response = await _httpClient.PostAsJsonAsync(
            "chat/completions",
            payload,
            cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            var errorBody = await response.Content.ReadAsStringAsync(
                cancellationToken);

            _logger.LogWarning(
                "Darvareh request failed. StatusCode: {StatusCode}, Body: {Body}",
                (int)response.StatusCode,
                errorBody);

            throw new HttpRequestException(
                $"AI provider returned status {(int)response.StatusCode}.");
        }

        var result = await response.Content.ReadFromJsonAsync<AiCompletionResponse>(
            cancellationToken: cancellationToken);

        var answer = result?.Choices.FirstOrDefault()?.Message.Content;

        if (string.IsNullOrWhiteSpace(answer))
        {
            throw new InvalidOperationException(
                "The AI response did not contain any text.");
        }

        TokenUsage? usage = null;

        if (result?.Usage is not null)
        {
            usage = new TokenUsage(
                result.Usage.PromptTokens,
                result.Usage.CompletionTokens,
                result.Usage.TotalTokens);
        }

        return new ChatResponse(
            answer,
            result?.Model ?? _options.Model,
            usage);
    }

    public async IAsyncEnumerable<string> StreamAsync(
        string message,
        string? systemPrompt,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var payload = new
        {
            model = _options.Model,
            messages = BuildMessages(message, systemPrompt),
            temperature = 0.3,
            max_tokens = 1000,
            stream = true
        };

        using var request = new HttpRequestMessage(
            HttpMethod.Post,
            "chat/completions")
        {
            Content = JsonContent.Create(payload)
        };

        using var response = await _httpClient.SendAsync(
            request,
            HttpCompletionOption.ResponseHeadersRead,
            cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            var errorBody = await response.Content.ReadAsStringAsync(
                cancellationToken);

            _logger.LogWarning(
                "Streaming request failed. StatusCode: {StatusCode}, Body: {Body}",
                (int)response.StatusCode,
                errorBody);

            throw new HttpRequestException(
                $"AI provider returned status {(int)response.StatusCode}.");
        }

        await using var stream = await response.Content.ReadAsStreamAsync(
            cancellationToken);

        using var reader = new StreamReader(stream);

        while (!reader.EndOfStream)
        {
            var line = await reader.ReadLineAsync(cancellationToken);

            if (string.IsNullOrWhiteSpace(line) ||
                !line.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var data = line["data:".Length..].Trim();

            if (data == "[DONE]")
            {
                yield break;
            }

            string? content = null;

            try
            {
                using var document = JsonDocument.Parse(data);

                content = document.RootElement
                    .GetProperty("choices")[0]
                    .GetProperty("delta")
                    .TryGetProperty("content", out var contentElement)
                        ? contentElement.GetString()
                        : null;
            }
            catch (JsonException exception)
            {
                _logger.LogDebug(
                    exception,
                    "An SSE chunk could not be parsed.");
            }

            if (!string.IsNullOrEmpty(content))
            {
                yield return content;
            }
        }
    }

    private static object[] BuildMessages(
        string message,
        string? systemPrompt)
    {
        var messages = new List<object>();

        if (!string.IsNullOrWhiteSpace(systemPrompt))
        {
            messages.Add(new
            {
                role = "system",
                content = systemPrompt
            });
        }

        messages.Add(new
        {
            role = "user",
            content = message
        });

        return messages.ToArray();
    }
}

این سرویس دو روش دریافت پاسخ دارد:

  • CompleteAsync: پاسخ کامل را پس از پایان تولید دریافت می‌کند.
  • StreamAsync: متن را به‌صورت قطعه‌قطعه هنگام تولید دریافت می‌کند.

در روش Streaming از HttpCompletionOption.ResponseHeadersRead استفاده کرده‌ایم تا برنامه منتظر بارگیری کامل بدنه پاسخ نماند.

ثبت HttpClient و تنظیم Dependency Injection

فایل Program.cs را به شکل زیر تنظیم کنید:

using System.Net.Http.Headers;
using DarvarehAiApi.Options;
using DarvarehAiApi.Services;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

builder.Services.Configure<DarvarehOptions>(
    builder.Configuration.GetSection(DarvarehOptions.SectionName));

builder.Services.AddHttpClient<DarvarehAiClient>((serviceProvider, client) =>
{
    var options = serviceProvider
        .GetRequiredService<IOptions<DarvarehOptions>>()
        .Value;

    if (string.IsNullOrWhiteSpace(options.ApiKey))
    {
        throw new InvalidOperationException(
            "Darvareh API key has not been configured.");
    }

    if (string.IsNullOrWhiteSpace(options.Model))
    {
        throw new InvalidOperationException(
            "Darvareh model ID has not been configured.");
    }

    client.BaseAddress = new Uri(
        options.BaseUrl.TrimEnd('/') + "/");

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", options.ApiKey);

    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

    client.Timeout = TimeSpan.FromSeconds(90);
});

builder.Services.AddCors(options =>
{
    options.AddPolicy("Frontend", policy =>
    {
        policy
            .WithOrigins("https://example.com")
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.UseHttpsRedirection();
app.UseCors("Frontend");

app.MapControllers();

app.Run();

آدرس https://example.com را با دامنه واقعی Frontend خود جایگزین کنید.

استفاده از IHttpClientFactory مدیریت اتصال‌های HTTP، پیکربندی Client و یکپارچگی با Dependency Injection و Logging را ساده‌تر می‌کند. توضیحات کامل این سازوکار در مستندات رسمی IHttpClientFactory موجود است.

CORS را نیز فقط برای دامنه‌های موردنیاز فعال کنید. AllowAnyOrigin ممکن است برای نمونه‌های موقت مناسب به نظر برسد، اما در یک محصول واقعی بهتر است مبدأهای مجاز را مشخص کنید. جزئیات رفتار CORS در مستندات ASP.NET Core توضیح داده شده است.

ساخت Endpoint معمولی چت

فایل Controllers/AiController.cs را ایجاد کنید:

using System.Text.Json;
using DarvarehAiApi.Models;
using DarvarehAiApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace DarvarehAiApi.Controllers;

[ApiController]
[Route("api/ai")]
public sealed class AiController : ControllerBase
{
    private readonly DarvarehAiClient _aiClient;

    public AiController(DarvarehAiClient aiClient)
    {
        _aiClient = aiClient;
    }

    [HttpPost("chat")]
    [ProducesResponseType<ChatResponse>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status502BadGateway)]
    public async Task<ActionResult<ChatResponse>> Chat(
        [FromBody] ChatRequest request,
        CancellationToken cancellationToken)
    {
        var result = await _aiClient.CompleteAsync(
            request.Message,
            request.SystemPrompt,
            cancellationToken);

        return Ok(result);
    }

    [HttpPost("chat/stream")]
    public async Task Stream(
        [FromBody] ChatRequest request,
        CancellationToken cancellationToken)
    {
        Response.StatusCode = StatusCodes.Status200OK;
        Response.ContentType = "text/event-stream";
        Response.Headers.CacheControl = "no-cache";
        Response.Headers.Append("X-Accel-Buffering", "no");

        await foreach (var token in _aiClient.StreamAsync(
            request.Message,
            request.SystemPrompt,
            cancellationToken))
        {
            var data = JsonSerializer.Serialize(new { token });

            await Response.WriteAsync(
                $"data: {data}\n\n",
                cancellationToken);

            await Response.Body.FlushAsync(cancellationToken);
        }

        await Response.WriteAsync(
            "data: [DONE]\n\n",
            cancellationToken);
    }
}

اکنون برنامه دو Endpoint دارد:

POST /api/ai/chat
POST /api/ai/chat/stream

اولی یک پاسخ JSON کامل برمی‌گرداند و دومی پاسخ را با Server-Sent Events یا SSE ارسال می‌کند.

اجرای پروژه

دستور زیر را اجرا کنید:

dotnet run

آدرس HTTPS یا HTTP نمایش‌داده‌شده در ترمینال را یادداشت کنید.

آزمایش Endpoint چت با cURL

فرض کنیم پروژه روی آدرس زیر اجرا شده است:

http://localhost:5000

درخواست آزمایشی:

curl -X POST "http://localhost:5000/api/ai/chat" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "سه کاربرد عملی هوش مصنوعی در فروشگاه اینترنتی را توضیح بده.",
    "systemPrompt": "تو یک مشاور فنی هستی. پاسخ را کوتاه، دقیق و فارسی بنویس."
  }'

نمونه پاسخ Backend:

{
  "answer": "سه کاربرد عملی شامل تولید توضیحات محصول، پاسخ‌گویی خودکار به مشتریان و تحلیل نظرات کاربران است.",
  "model": "YOUR_MODEL_ID",
  "usage": {
    "promptTokens": 42,
    "completionTokens": 58,
    "totalTokens": 100
  }
}

مقادیر واقعی پاسخ و توکن‌ها به مدل و درخواست شما بستگی دارند.

آزمایش Streaming با cURL

برای جلوگیری از بافرشدن خروجی cURL از گزینه -N استفاده کنید:

curl -N -X POST "http://localhost:5000/api/ai/chat/stream" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "یک توضیح ساده درباره Dependency Injection بنویس.",
    "systemPrompt": "پاسخ را برای یک برنامه‌نویس تازه‌کار بنویس."
  }'

خروجی به‌تدریج نمایش داده می‌شود:

data: {"token":"Dependency"}

data: {"token":" Injection"}

data: {"token":" الگویی"}

data: {"token":" برای..."}

data: [DONE]

چرا Streaming تجربه کاربری را بهتر می‌کند؟

در پاسخ معمولی، کاربر باید تا تولید کامل متن منتظر بماند. اگر تولید پاسخ ۱۵ ثانیه طول بکشد، در این مدت چیزی نمایش داده نمی‌شود.

در Streaming، اولین بخش متن ممکن است خیلی زودتر دریافت شود. بنابراین کاربر احساس می‌کند سیستم سریع‌تر پاسخ داده است، حتی اگر زمان کل تولید تغییر زیادی نکرده باشد.

Streaming برای این کاربردها مناسب است:

  • چت‌بات
  • دستیار تولید محتوا
  • توضیح کد
  • تولید گزارش
  • پاسخ‌های طولانی
  • دستیار جست‌وجو

برای طبقه‌بندی متن یا استخراج یک JSON کوتاه، پاسخ عادی معمولاً ساده‌تر و کافی است.

اتصال Frontend به Endpoint معمولی

نمونه درخواست با JavaScript:

async function sendMessage(message) {
  const response = await fetch("https://api.example.com/api/ai/chat", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      message,
      systemPrompt:
        "پاسخ را دقیق، کاربردی و به زبان فارسی ارائه کن."
    })
  });

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  return await response.json();
}

sendMessage("ASP.NET Core چیست؟")
  .then(result => console.log(result.answer))
  .catch(error => console.error(error));

API Key در این کد وجود ندارد. Frontend تنها به Backend شما متصل می‌شود و کلید درواره داخل ASP.NET Core باقی می‌ماند.

خواندن پاسخ Streaming در Frontend

از آنجا که درخواست ما POST است، می‌توانیم پاسخ Streaming را با fetch و ReadableStream بخوانیم:

async function streamMessage(message, onToken) {
  const response = await fetch(
    "https://api.example.com/api/ai/chat/stream",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        message,
        systemPrompt: "پاسخ را فارسی بنویس."
      })
    }
  );

  if (!response.ok || !response.body) {
    throw new Error(`Streaming failed: ${response.status}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { value, done } = await reader.read();

    if (done) {
      break;
    }

    buffer += decoder.decode(value, { stream: true });

    const events = buffer.split("\n\n");
    buffer = events.pop() ?? "";

    for (const event of events) {
      const line = event
        .split("\n")
        .find(item => item.startsWith("data:"));

      if (!line) {
        continue;
      }

      const data = line.slice(5).trim();

      if (data === "[DONE]") {
        return;
      }

      const parsed = JSON.parse(data);
      onToken(parsed.token);
    }
  }
}

let fullText = "";

streamMessage(
  "مزایای معماری Clean Architecture را توضیح بده.",
  token => {
    fullText += token;
    document.querySelector("#answer").textContent = fullText;
  }
);

متغیر buffer مهم است؛ زیرا هر قطعه شبکه لزوماً دقیقاً شامل یک رویداد کامل SSE نیست. ممکن است یک رویداد بین چند Chunk تقسیم شده باشد.

مدیریت تاریخچه مکالمه

نمونه فعلی فقط یک پیام کاربر را ارسال می‌کند. برای ساخت چت‌بات واقعی باید تاریخچه مکالمه را نیز نگه دارید.

ساختار پیام‌ها معمولاً به این صورت است:

[
  {
    "role": "system",
    "content": "تو دستیار پشتیبانی فروشگاه هستی."
  },
  {
    "role": "user",
    "content": "زمان ارسال سفارش چقدر است؟"
  },
  {
    "role": "assistant",
    "content": "سفارش‌ها معمولاً در دو تا چهار روز کاری ارسال می‌شوند."
  },
  {
    "role": "user",
    "content": "برای شهرستان چطور؟"
  }
]

پیام آخر بدون تاریخچه، مبهم است. مدل باید بداند عبارت «برای شهرستان چطور؟» به زمان ارسال سفارش اشاره می‌کند.

برای نگهداری مکالمه می‌توانید از موارد زیر استفاده کنید:

  • حافظه موقت برای نمونه اولیه
  • Redis برای مکالمات کوتاه‌مدت
  • PostgreSQL یا SQL Server برای ذخیره پایدار
  • خلاصه‌سازی پیام‌های قدیمی برای کاهش توکن
  • ترکیب خلاصه مکالمه با چند پیام اخیر

همه پیام‌های یک مکالمه طولانی را برای همیشه ارسال نکنید. این کار هزینه و زمان پاسخ را افزایش می‌دهد و ممکن است از ظرفیت Context Window مدل عبور کند.

ساخت Endpoint خلاصه‌سازی متن

همان سرویس را می‌توان برای قابلیت‌های دیگر استفاده کرد. نمونه زیر یک Endpoint خلاصه‌سازی می‌سازد:

using System.ComponentModel.DataAnnotations;

namespace DarvarehAiApi.Models;

public sealed class SummarizeRequest
{
    [Required]
    [StringLength(30000, MinimumLength = 20)]
    public string Text { get; init; } = string.Empty;

    [Range(1, 10)]
    public int MaxParagraphs { get; init; } = 3;
}

اکشن زیر را به AiController اضافه کنید:

[HttpPost("summarize")]
public async Task<ActionResult<ChatResponse>> Summarize(
    [FromBody] SummarizeRequest request,
    CancellationToken cancellationToken)
{
    var prompt = $"""
        متن زیر را حداکثر در {request.MaxParagraphs} پاراگراف خلاصه کن.

        الزامات:
        - اطلاعات مهم حفظ شوند.
        - مطلب جدیدی به متن اضافه نشود.
        - پاسخ به زبان فارسی روان باشد.

        متن:
        {request.Text}
        """;

    var result = await _aiClient.CompleteAsync(
        prompt,
        "تو یک ویراستار حرفه‌ای و دقیق هستی.",
        cancellationToken);

    return Ok(result);
}

با همین الگو می‌توانید Endpointهای دیگری برای تولید عنوان، بازنویسی متن، استخراج کلیدواژه یا پاسخ‌گویی بسازید.

تولید خروجی JSON ساختاریافته

در بسیاری از پروژه‌ها، پاسخ متنی آزاد کافی نیست. برای مثال ممکن است بخواهید یک نظر مشتری به ساختار زیر تبدیل شود:

{
  "category": "delivery",
  "sentiment": "negative",
  "priority": 4,
  "summary": "سفارش با تأخیر تحویل داده شده است"
}

برای این کار باید مدل را به تولید JSON محدود کنید و سپس خروجی را در Backend اعتبارسنجی کنید.

مدل C# خروجی:

using System.Text.Json.Serialization;

namespace DarvarehAiApi.Models;

public sealed class FeedbackAnalysis
{
    [JsonPropertyName("category")]
    public string Category { get; init; } = string.Empty;

    [JsonPropertyName("sentiment")]
    public string Sentiment { get; init; } = string.Empty;

    [JsonPropertyName("priority")]
    public int Priority { get; init; }

    [JsonPropertyName("summary")]
    public string Summary { get; init; } = string.Empty;
}

یک متد جدید به DarvarehAiClient اضافه کنید:

public async Task<T> CompleteJsonAsync<T>(
    string prompt,
    CancellationToken cancellationToken)
{
    var payload = new
    {
        model = _options.Model,
        messages = new object[]
        {
            new
            {
                role = "system",
                content = """
                    فقط یک JSON معتبر تولید کن.
                    هیچ متن، توضیح یا Markdown خارج از JSON ننویس.
                    """
            },
            new
            {
                role = "user",
                content = prompt
            }
        },
        temperature = 0,
        max_tokens = 500,
        response_format = new
        {
            type = "json_object"
        }
    };

    using var response = await _httpClient.PostAsJsonAsync(
        "chat/completions",
        payload,
        cancellationToken);

    response.EnsureSuccessStatusCode();

    var result = await response.Content
        .ReadFromJsonAsync<AiCompletionResponse>(
            cancellationToken: cancellationToken);

    var json = result?
        .Choices
        .FirstOrDefault()?
        .Message
        .Content;

    if (string.IsNullOrWhiteSpace(json))
    {
        throw new InvalidOperationException(
            "The model returned an empty JSON response.");
    }

    var parsed = JsonSerializer.Deserialize<T>(
        json,
        new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        });

    return parsed ?? throw new JsonException(
        "The model response could not be deserialized.");
}

سپس Endpoint تحلیل بازخورد را اضافه کنید:

public sealed class FeedbackRequest
{
    [Required]
    [StringLength(5000, MinimumLength = 3)]
    public string Text { get; init; } = string.Empty;
}
[HttpPost("analyze-feedback")]
public async Task<ActionResult<FeedbackAnalysis>> AnalyzeFeedback(
    [FromBody] FeedbackRequest request,
    CancellationToken cancellationToken)
{
    var prompt = $"""
        بازخورد زیر را تحلیل کن:

        {request.Text}

        دقیقاً این ساختار را برگردان:
        {{
          "category": "product | delivery | payment | support | other",
          "sentiment": "positive | neutral | negative",
          "priority": 1,
          "summary": "خلاصه کوتاه فارسی"
        }}

        priority باید عددی بین 1 تا 5 باشد.
        """;

    var result = await _aiClient.CompleteJsonAsync<FeedbackAnalysis>(
        prompt,
        cancellationToken);

    if (result.Priority is < 1 or > 5)
    {
        return Problem(
            title: "Invalid model output",
            detail: "The returned priority was outside the valid range.",
            statusCode: StatusCodes.Status502BadGateway);
    }

    return Ok(result);
}

پارامتر response_format باید توسط مدل انتخابی پشتیبانی شود. اگر مدل موردنظر این قابلیت را ندارد، می‌توانید آن را حذف کنید و همچنان با پرامپت دقیق، خروجی JSON بخواهید؛ اما در هر دو حالت اعتبارسنجی سمت سرور ضروری است.

هرگز خروجی مدل را بدون بررسی مستقیماً وارد پایگاه داده یا منطق حساس برنامه نکنید.

پرامپت مناسب برای برنامه‌های C#

کیفیت پاسخ فقط به مدل وابسته نیست. ساختار پرامپت (Prompt) نیز اهمیت زیادی دارد.

یک پرامپت مناسب معمولاً شامل این بخش‌ها است:

  • نقش مدل
  • هدف دقیق
  • اطلاعات ورودی
  • محدودیت‌ها
  • قالب خروجی
  • معیار پذیرش

نمونه ضعیف:

این نظر را تحلیل کن.

نمونه بهتر:

تو تحلیل‌گر بازخورد مشتری هستی.

نظر کاربر را از نظر موضوع، احساس و اولویت بررسی کن.

موضوع فقط یکی از این مقادیر باشد:
product, delivery, payment, support, other

احساس فقط یکی از این مقادیر باشد:
positive, neutral, negative

اولویت عددی بین 1 تا 5 باشد.

فقط JSON معتبر برگردان.

در برنامه‌های واقعی، بخش ثابت پرامپت را در Backend نگه دارید و فقط داده موردنیاز کاربر را به آن اضافه کنید.

مدیریت خطا با Problem Details

در Program.cs این موارد را ثبت کردیم:

builder.Services.AddProblemDetails();

و:

app.UseExceptionHandler();
app.UseStatusCodePages();

ASP.NET Core می‌تواند خطاهای HTTP را با قالب استاندارد Problem Details برگرداند. مایکروسافت نحوه پیکربندی این پاسخ‌ها را در راهنمای مدیریت خطاهای ASP.NET Core API توضیح داده است.

با این حال بهتر است خطاهای سرویس هوش مصنوعی را به وضعیت‌های مناسب تبدیل کنید:

وضعیت بالادستیرفتار پیشنهادی Backend
400بررسی ورودی، مدل و ساختار درخواست
401بررسی API Key در تنظیمات سرور
403بررسی مجوز حساب یا مدل
429اعمال Backoff یا صف درخواست
500 تا 599ثبت خطا و اجرای Fallback کنترل‌شده
Timeoutلغو درخواست و اعلام خطای موقت
پاسخ نامعتبرثبت ساختار پاسخ و بازگرداندن 502

جزئیات داخلی، API Key یا بدنه کامل اطلاعات حساس را در پاسخ کاربر نمایش ندهید.

استفاده صحیح از CancellationToken

در تمام متدهای Async، CancellationToken را از Controller تا HttpClient منتقل کردیم.

این کار باعث می‌شود اگر کاربر صفحه را ببندد یا درخواست را لغو کند، Backend نیز بتواند پردازش و اتصال بالادستی را متوقف کند. در درخواست‌های Streaming این موضوع مهم‌تر است؛ زیرا ممکن است اتصال برای مدت طولانی باز بماند.

الگوی درست:

public async Task<IActionResult> Example(
    CancellationToken cancellationToken)
{
    var result = await _service.RunAsync(cancellationToken);
    return Ok(result);
}

الگوی نامناسب:

public async Task<IActionResult> Example()
{
    var result = await _service.RunAsync(
        CancellationToken.None);

    return Ok(result);
}

تنظیم Timeout

در نمونه HttpClient مقدار زیر را قرار دادیم:

client.Timeout = TimeSpan.FromSeconds(90);

این عدد باید متناسب با کاربرد تنظیم شود:

  • طبقه‌بندی کوتاه: حدود ۱۵ تا ۳۰ ثانیه
  • چت عادی: حدود ۳۰ تا ۹۰ ثانیه
  • پاسخ طولانی: بیشتر، همراه با Streaming
  • پردازش غیرهم‌زمان: استفاده از Job Queue به‌جای اتصال طولانی

Timeout خیلی کوتاه باعث قطع درخواست‌های معتبر می‌شود و Timeout بسیار بلند منابع سرور را برای مدت بیشتری درگیر نگه می‌دارد.

Retry؛ چه زمانی درخواست را دوباره ارسال کنیم؟

Retry برای همه خطاها مناسب نیست.

برای نمونه، تکرار درخواست در وضعیت‌های زیر معمولاً بی‌فایده است:

  • API Key نامعتبر
  • Model ID اشتباه
  • ساختار JSON نامعتبر
  • ورودی خارج از محدودیت
  • درخواست غیرمجاز

Retry ممکن است برای خطاهای موقت زیر مفید باشد:

  • بعضی خطاهای ۵xx
  • خطای اتصال موقت
  • محدودیت مصرف لحظه‌ای با تأخیر مناسب
  • قطع موقت شبکه

برای جلوگیری از ارسال چندباره درخواست، تعداد Retry را محدود و بین تلاش‌ها Backoff ایجاد کنید. در عملیات حساس، Idempotency و اثر تکرار درخواست را نیز در نظر بگیرید.

جلوگیری از افشای API Key در لاگ‌ها

Header احراز هویت نباید در لاگ ذخیره شود:

Authorization: Bearer YOUR_DARVAREH_API_KEY

همچنین در ذخیره بدنه درخواست‌ها احتیاط کنید؛ زیرا پرامپت کاربر ممکن است شامل داده‌های شخصی یا اطلاعات داخلی کسب‌وکار باشد.

پیشنهاد عملی:

  • API Key را هیچ‌وقت Log نکنید.
  • بدنه کامل درخواست را به‌صورت پیش‌فرض ثبت نکنید.
  • برای هر درخواست یک شناسه پیگیری بسازید.
  • زمان پاسخ، مدل، وضعیت و تعداد توکن را ثبت کنید.
  • داده‌های حساس را پیش از ثبت حذف یا ماسک کنید.
  • سطح Logging محیط Development و Production را جدا نگه دارید.

کنترل هزینه و مصرف توکن

هزینه درخواست هوش مصنوعی معمولاً به مدل و تعداد توکن‌های ورودی و خروجی وابسته است.

برای کنترل مصرف:

  • طول ورودی را محدود کنید.
  • max_tokens را متناسب با کاربرد تنظیم کنید.
  • تاریخچه غیرضروری را ارسال نکنید.
  • پیام‌های قدیمی را خلاصه کنید.
  • برای کارهای ساده از مدل اقتصادی‌تر استفاده کنید.
  • پاسخ‌های تکراری را Cache کنید.
  • مصرف هر کاربر یا سازمان را ثبت کنید.
  • سقف روزانه یا ماهانه تعریف کنید.
  • درخواست‌های غیرضروری را پیش از ارسال رد کنید.

برای مشاهده قیمت و مدل‌های قابل‌دسترسی، صفحه مدل‌های درواره را بررسی کنید.

انتخاب Temperature مناسب

پارامتر temperature میزان تنوع احتمالی پاسخ را کنترل می‌کند.

کاربردTemperature پیشنهادی
استخراج JSON0 تا 0.2
طبقه‌بندی0 تا 0.2
خلاصه‌سازی دقیق0.1 تا 0.4
پاسخ‌گویی عمومی0.3 تا 0.7
ایده‌پردازی0.7 تا 1
متن خلاقانه0.8 تا 1.2

این اعداد نقطه شروع هستند و نتیجه واقعی به مدل بستگی دارد. برای پروژه مهم، مجموعه‌ای از ورودی‌های آزمایشی ثابت بسازید و تنظیمات مختلف را روی آن‌ها ارزیابی کنید.

افزودن Rate Limiting

اگر Endpoint بدون محدودیت در دسترس باشد، یک کاربر می‌تواند تعداد زیادی درخواست ایجاد کند و هزینه سرویس را افزایش دهد.

در ASP.NET Core می‌توانید Rate Limiting داخلی را فعال کنید:

using System.Threading.RateLimiting;

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("ai-policy", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey:
                httpContext.User.Identity?.Name ??
                httpContext.Connection.RemoteIpAddress?.ToString() ??
                "anonymous",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 20,
                Window = TimeSpan.FromMinutes(1),
                QueueLimit = 0
            }));
});

در Pipeline:

app.UseRateLimiter();

روی Controller یا Action:

using Microsoft.AspNetCore.RateLimiting;

[EnableRateLimiting("ai-policy")]
[HttpPost("chat")]
public async Task<ActionResult<ChatResponse>> Chat(
    [FromBody] ChatRequest request,
    CancellationToken cancellationToken)
{
    var result = await _aiClient.CompleteAsync(
        request.Message,
        request.SystemPrompt,
        cancellationToken);

    return Ok(result);
}

محدودیت مبتنی بر IP برای همه سناریوها کافی نیست. اگر نرم‌افزار حساب کاربری دارد، بهتر است محدودیت را بر اساس شناسه کاربر، سازمان یا اشتراک اعمال کنید.

افزودن احراز هویت

در یک برنامه واقعی، Endpoint هوش مصنوعی نباید لزوماً برای همه کاربران عمومی باشد.

می‌توانید از روش‌های زیر استفاده کنید:

  • JWT Bearer Authentication
  • Cookie Authentication
  • API Key داخلی
  • Identity Provider سازمانی
  • سیاست‌های Role و Permission

بعد از احراز هویت، اطلاعات زیر را برای کنترل مصرف ثبت کنید:

UserId
OrganizationId
Model
PromptTokens
CompletionTokens
TotalTokens
DurationMs
StatusCode
CreatedAt

این داده‌ها برای مشاهده هزینه، تشخیص رفتار غیرعادی و بهبود عملکرد مفید هستند.

کش‌کردن پاسخ‌ها

اگر یک ورودی ثابت بارها ارسال می‌شود، Cache می‌تواند هزینه و زمان پاسخ را کاهش دهد.

یک کلید کش مناسب باید حداقل به این موارد وابسته باشد:

Model + SystemPrompt + UserPrompt + Parameters + PromptVersion

نمونه ساخت Hash:

using System.Security.Cryptography;
using System.Text;

static string CreateCacheKey(string input)
{
    var bytes = SHA256.HashData(
        Encoding.UTF8.GetBytes(input));

    return Convert.ToHexString(bytes);
}

موارد مناسب برای کش:

  • تولید توضیح ثابت
  • پاسخ به سؤال‌های متداول
  • طبقه‌بندی داده‌ای که تغییر نمی‌کند
  • خلاصه یک سند ثابت

مواردی که ممکن است برای کش مناسب نباشند:

  • پاسخ شخصی‌سازی‌شده
  • اطلاعات لحظه‌ای
  • مکالمه وابسته به تاریخچه
  • داده‌ای که مرتب تغییر می‌کند

برای Cache توزیع‌شده در چند Instance، Redis انتخاب متداولی است.

نسخه‌بندی پرامپت‌ها

پرامپت‌های اصلی را به‌صورت متن پراکنده در Controllerها قرار ندهید. بهتر است آن‌ها را مانند کد محصول مدیریت کنید.

برای هر پرامپت اطلاعات زیر را نگه دارید:

Name: feedback-analysis
Version: 3
Model: YOUR_MODEL_ID
Temperature: 0
MaxTokens: 500
ExpectedSchema: FeedbackAnalysis

هنگام ثبت نتیجه نیز نسخه پرامپت را ذخیره کنید. در این صورت اگر کیفیت خروجی پس از تغییر کاهش پیدا کند، می‌توانید علت را پیدا کنید یا نسخه قبلی را بازگردانید.

تست واحد سرویس‌های هوش مصنوعی

تست‌های عادی نباید همیشه به API واقعی متصل شوند؛ زیرا:

  • هزینه ایجاد می‌کنند.
  • نتیجه ممکن است اندکی تغییر کند.
  • تست به شبکه وابسته می‌شود.
  • اجرای Pipeline کندتر خواهد شد.

یک Interface تعریف کنید:

public interface IAiClient
{
    Task<ChatResponse> CompleteAsync(
        string message,
        string? systemPrompt,
        CancellationToken cancellationToken);
}

سپس DarvarehAiClient این Interface را پیاده‌سازی کند:

public sealed class DarvarehAiClient : IAiClient
{
    // implementation
}

در تست از Fake استفاده کنید:

public sealed class FakeAiClient : IAiClient
{
    public Task<ChatResponse> CompleteAsync(
        string message,
        string? systemPrompt,
        CancellationToken cancellationToken)
    {
        return Task.FromResult(
            new ChatResponse(
                "پاسخ آزمایشی",
                "test-model",
                new TokenUsage(10, 5, 15)));
    }
}

برای بررسی اتصال واقعی، تعداد محدودی Integration Test جداگانه اجرا کنید.

تست کیفیت خروجی مدل

موفق‌بودن وضعیت HTTP به معنی مناسب‌بودن پاسخ نیست. باید کیفیت خروجی را نیز ارزیابی کنید.

برای مثال در Endpoint تحلیل بازخورد بررسی کنید:

  • JSON معتبر باشد.
  • Category یکی از مقادیر مجاز باشد.
  • Sentiment مقدار معتبر داشته باشد.
  • Priority بین ۱ تا ۵ باشد.
  • Summary خالی نباشد.
  • زمان پاسخ از حد موردنظر بیشتر نشود.
  • مصرف توکن قابل‌قبول باشد.

مجموعه‌ای از نمونه‌های واقعی و لبه‌ای تهیه کنید:

نظر بسیار کوتاه
نظر طولانی
متن نامرتبط
نظر دارای چند موضوع
متن فارسی و انگلیسی ترکیبی
ورودی خالی
ورودی با نویسه‌های غیرمعمول

پس از تغییر مدل یا پرامپت، این مجموعه را دوباره اجرا کنید.

استقرار با Docker

نمونه Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY ["DarvarehAiApi.csproj", "./"]
RUN dotnet restore "DarvarehAiApi.csproj"

COPY . .
RUN dotnet publish "DarvarehAiApi.csproj" \
    -c Release \
    -o /app/publish \
    /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .

ENTRYPOINT ["dotnet", "DarvarehAiApi.dll"]

هنگام اجرا، اطلاعات محرمانه را از Environment Variable وارد کنید:

docker run -p 8080:8080 \
  -e Darvareh__ApiKey="YOUR_DARVAREH_API_KEY" \
  -e Darvareh__Model="YOUR_MODEL_ID" \
  darvareh-ai-api

برای پروژه خود، نسخه Image دات‌نت را با Target Framework پروژه هماهنگ کنید.

چک‌لیست آماده‌سازی برای Production

قبل از انتشار سرویس، این موارد را بررسی کنید:

  • API Key خارج از Repository ذخیره شده است.
  • Frontend مستقیماً به API مدل متصل نمی‌شود.
  • ورودی‌ها محدود و اعتبارسنجی می‌شوند.
  • Endpointها احراز هویت دارند.
  • Rate Limiting فعال است.
  • Timeout مشخص شده است.
  • CancellationToken منتقل می‌شود.
  • Retry فقط برای خطاهای موقت اجرا می‌شود.
  • پاسخ JSON مدل اعتبارسنجی می‌شود.
  • لاگ‌ها شامل API Key نیستند.
  • داده‌های حساس کاربران بدون ضرورت ذخیره نمی‌شوند.
  • میزان مصرف توکن ثبت می‌شود.
  • سقف هزینه برای کاربران تعریف شده است.
  • خطاهای بالادستی به پاسخ قابل‌فهم تبدیل می‌شوند.
  • CORS فقط برای دامنه‌های لازم فعال است.
  • Streaming روی Reverse Proxy آزمایش شده است.
  • تست‌های کیفیت برای مدل و پرامپت وجود دارند.
  • مدل جایگزین یا رفتار Fallback تعریف شده است.

خطاهای رایج در اتصال C# به API هوش مصنوعی

خطای 401 Unauthorized

دلایل احتمالی:

  • API Key ثبت نشده است.
  • API Key اشتباه است.
  • متغیر محیطی با نام نادرست تعریف شده است.
  • Header احراز هویت ارسال نمی‌شود.

ساختار صحیح Header:

Authorization: Bearer YOUR_DARVAREH_API_KEY

خطای 404 Not Found

Base URL و مسیر Endpoint را بررسی کنید:

Base URL:
https://api.darvareh.ir/v1

Endpoint:
chat/completions

ترکیب نهایی:

https://api.darvareh.ir/v1/chat/completions

وجود یا نبودن / بین Base URL و مسیر را نیز بررسی کنید.

خطای مدل نامعتبر

مقدار زیر باید با شناسه مدل موجود در درواره جایگزین شود:

YOUR_MODEL_ID

مدل‌ها و قیمت به‌مرور تغییر می‌کنند؛ بنابراین شناسه را از صفحه مدل‌های درواره بردارید.

پاسخ دیر دریافت می‌شود

این موارد را بررسی کنید:

  • مدل برای کاربرد شما بیش‌ازحد سنگین نباشد.
  • مقدار max_tokens بسیار بزرگ نباشد.
  • تاریخچه طولانی غیرضروری ارسال نشود.
  • برای پاسخ طولانی از Streaming استفاده شود.
  • زمان DNS، اتصال و سرویس بالادستی جداگانه بررسی شود.

Streaming یک‌باره نمایش داده می‌شود

ممکن است Reverse Proxy پاسخ را Buffer کند.

اقدامات احتمالی:

  • Header مربوط به غیرفعال‌کردن Buffering را تنظیم کنید.
  • تنظیمات Nginx یا Proxy را بررسی کنید.
  • FlushAsync را بعد از هر رویداد اجرا کنید.
  • فشرده‌سازی پاسخ SSE را آزمایش کنید.
  • ابتدا Endpoint را مستقیماً و بدون Proxy تست کنید.

JSON مدل قابل Deserialize نیست

علت‌های رایج:

  • مدل متن توضیحی اطراف JSON نوشته است.
  • نام فیلدها تغییر کرده است.
  • خروجی ناقص شده است.
  • max_tokens کافی نیست.
  • مدل انتخابی از خروجی JSON پشتیبانی مناسبی ندارد.

پرامپت را دقیق‌تر کنید، Temperature را کاهش دهید و خروجی را قبل از استفاده اعتبارسنجی کنید.

آیا باید از SDK استفاده کنیم یا HttpClient؟

برای اتصال به API سازگار با ساختار استاندارد، هر دو روش ممکن است.

مزایای HttpClient:

  • وابستگی کمتر
  • کنترل کامل بر Header و Payload
  • مشاهده واضح ساختار درخواست
  • مدیریت ساده Endpointهای مختلف
  • کنترل مستقیم Streaming

مزایای SDK:

  • مدل‌های آماده برای Request و Response
  • کدنویسی کمتر
  • امکانات سطح بالاتر
  • مدیریت ساده‌تر بعضی قابلیت‌ها

برای آموزش، از HttpClient استفاده کردیم تا همه اجزای ارتباط شفاف باشند. در پروژه واقعی می‌توانید از SDK سازگار نیز استفاده کنید، به شرط آنکه امکان تنظیم Base URL و Model ID دلخواه را داشته باشد.

چرا از درواره برای پروژه C# استفاده کنیم؟

اگر پروژه شما به مدل‌های هوش مصنوعی نیاز دارد، اتصال جداگانه به سرویس‌های مختلف می‌تواند مدیریت کد، تنظیمات و هزینه را پیچیده کند.

درواره یک نقطه اتصال یکپارچه برای استفاده از مدل‌های مختلف فراهم می‌کند. در نتیجه می‌توانید:

  • با یک Base URL ثابت کار کنید.
  • API Key را در Backend نگه دارید.
  • مدل متناسب با کاربرد را انتخاب کنید.
  • بدون بازنویسی کل معماری، مدل را تغییر دهید.
  • از C#، ASP.NET Core و سایر فناوری‌های Backend استفاده کنید.
  • هزینه و مدل‌ها را در یک مسیر مشخص بررسی کنید.

برای شروع، وارد درواره شوید، API Key بسازید و مدل موردنظر خود را از صفحه مدل‌ها انتخاب کنید.

پرسش‌های متداول

آیا می‌توان با C# برنامه هوش مصنوعی ساخت؟

بله. C# و ASP.NET Core برای ساخت چت‌بات، دستیار سازمانی، تحلیل‌گر متن، ابزار تولید محتوا و سرویس‌های هوش مصنوعی مناسب هستند. ارتباط با مدل‌ها از طریق درخواست HTTP انجام می‌شود.

آیا برای استفاده از هوش مصنوعی در C# به پایتون نیاز داریم؟

خیر. اگر مدل از طریق API در دسترس باشد، می‌توانید تمام ارتباط و منطق Backend را مستقیماً با C# پیاده‌سازی کنید.

آیا API Key را می‌توان در Blazor WebAssembly قرار داد؟

خیر. کد و تنظیمات Blazor WebAssembly در مرورگر کاربر قابل‌مشاهده است. API Key باید در Backend، Blazor Server یا یک سرویس واسط امن نگه‌داری شود.

تفاوت پاسخ عادی و Streaming چیست؟

در پاسخ عادی، متن کامل پس از پایان تولید ارسال می‌شود. در Streaming، بخش‌های متن هنگام تولید به کاربر می‌رسند و تجربه چت سریع‌تر به نظر می‌رسد.

آیا می‌توان تاریخچه چت را در SQL Server ذخیره کرد؟

بله. می‌توانید مکالمه، پیام‌ها، نقش هر پیام، زمان ایجاد، مدل و میزان مصرف توکن را در SQL Server یا پایگاه داده دیگری ذخیره کنید.

آیا می‌توان مدل را بدون تغییر Controller عوض کرد؟

بله. در ساختار این آموزش، Model ID از Configuration خوانده می‌شود. می‌توانید آن را از Secret Store یا Environment Variable تغییر دهید.

آیا خروجی مدل همیشه JSON معتبر است؟

خیر. حتی اگر از مدل بخواهید JSON تولید کند، Backend باید خروجی را Deserialize و اعتبارسنجی کند. در صورت پشتیبانی مدل، استفاده از Structured Output یا JSON Mode قابلیت اطمینان را افزایش می‌دهد.

بهترین مدل برای ASP.NET Core کدام است؟

ASP.NET Core به مدل خاصی وابسته نیست. انتخاب مدل به کیفیت موردنیاز، سرعت، هزینه، Context Window و نوع کاربرد بستگی دارد. اطلاعات مدل‌های موجود را در صفحه مدل‌های درواره بررسی کنید.

چطور هزینه درخواست‌های هوش مصنوعی را کم کنیم؟

محدودکردن ورودی و خروجی، حذف تاریخچه غیرضروری، انتخاب مدل متناسب، کش‌کردن پاسخ‌ها و ثبت مصرف هر کاربر از مهم‌ترین روش‌های کنترل هزینه هستند.

آیا می‌توان این پروژه را روی Linux اجرا کرد؟

بله. ASP.NET Core چندسکویی است و می‌توان پروژه را روی Linux، Windows، Docker و محیط‌های ابری اجرا کرد.

آیا می‌توان از این ساختار در معماری Microservice استفاده کرد؟

بله. می‌توانید قابلیت هوش مصنوعی را در یک سرویس مستقل قرار دهید و سایر بخش‌های نرم‌افزار از طریق API یا Message Queue با آن ارتباط برقرار کنند.

آیا CORS از API محافظت می‌کند؟

خیر. CORS مشخص می‌کند کدام مبدأهای مرورگر اجازه ارسال درخواست Cross-Origin دارند، اما جایگزین احراز هویت، مجوزدهی و Rate Limiting نیست.

جمع‌بندی

C# و ASP.NET Core ابزارهای کاملی برای ساخت Backend برنامه‌های هوش مصنوعی فراهم می‌کنند. با استفاده از HttpClient، Dependency Injection، Configuration، Streaming و مدل‌های استاندارد JSON می‌توان یک سرویس تمیز و قابل‌توسعه ساخت.

در این آموزش یک پروژه عملی ایجاد کردیم که:

  • درخواست چت را دریافت می‌کند.
  • ورودی را اعتبارسنجی می‌کند.
  • API Key را خارج از کد نگه می‌دارد.
  • از طریق API درواره به مدل انتخابی متصل می‌شود.
  • پاسخ معمولی و Streaming ارائه می‌دهد.
  • خروجی JSON ساختاریافته تولید می‌کند.
  • مصرف توکن را در پاسخ ثبت می‌کند.
  • برای Rate Limiting، Logging، Testing و Production قابل‌گسترش است.

برای شروع پروژه، در درواره حساب کاربری ایجاد کنید، API Key بگیرید و شناسه مدل مناسب را از صفحه مدل‌ها انتخاب کنید.

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.