📑 فهرست مطالب
🎯 گام ۱: تحلیل رویکرد و استراتژی
مدل Qwen3.5-9B-The-Defiant-Fable-Uncensored-Heretic-NEO-IMATRIX-MAX-MTP-GGUF یک مدل زبانی بزرگ (LLM) در فرمت GGUF است. برای اجرای این مدل و استفاده از آن جهت استخراج محتوا، بهترین و سازگارترین راه با معماری فعلی پروژه شما، استفاده از Ollama به عنوان موتور اجرای محلی (Local Inference Engine) است.
۱. استخراج خام (Raw Extraction): استفاده از
IFileContentExtractor (که قبلاً طراحی کردیم) برای خواندن متن خام از PDF/DOCX/TXT.۲. پردازش هوشمند (LLM Processing): ارسال متن خام به مدل Qwen3.5 با یک System Prompt دقیق برای خلاصهسازی، استخراج موجودیتها (NER)، یا تبدیل به JSON ساختاریافته.
⚙️ گام ۲: آمادهسازی مدل GGUF در Ollama
از آنجا که Ollama به صورت بومی از فرمت GGUF پشتیبانی میکند، باید مدل را دانلود و در Ollama ایمپورت کنیم.
فایل .gguf را از لینک Hugging Face ارائه شده دانلود کرده و در مسیری مانند C:\Models\qwen3.5-defiant.gguf ذخیره کنید.
یک فایل متنی بدون پسوند به نام Modelfile در کنار فایل GGUF ایجاد کنید و محتوای زیر را در آن قرار دهید:
FROM C:/Models/qwen3.5-defiant.gguf # تنظیم پارامترهای بهینه برای استخراج محتوا PARAMETER temperature 0.2 PARAMETER top_p 0.9 PARAMETER num_ctx 8192 # تنظیم پرامپت سیستمی پیشفرض برای استخراج ساختاریافته SYSTEM """ تو یک دستیار هوشمند و دقیق برای استخراج و تحلیل محتوای اسناد هستی. وظیفه تو خواندن متن ورودی، درک عمیق آن، و استخراج اطلاعات کلیدی به صورت ساختاریافته و دقیق است. همیشه به زبان فارسی روان و حرفهای پاسخ بده، مگر اینکه خلاف آن درخواست شود. """
ترمینال یا CMD را باز کرده و دستور زیر را اجرا کنید تا مدل با یک نام مستعار (Alias) کوتاه ثبت شود:
ollama create qwen3.5-defiant:9b -f Modelfile
سپس برای اطمینان از صحت نصب، دستور ollama list را اجرا کنید.
📝 گام ۳: پیکربندی مدل در appsettings.json
اکنون باید این مدل جدید را به لیست مدلهای مجاز در پیکربندی پروژه اضافه کنیم تا سرویسها بتوانند از آن استفاده کنند.
"AiApiConfiguration": {
"Models": [
{
"Name": "Gemma",
"Url": "http://localhost:11434",
"LLM": "gemma:2b",
"Provider": "Ollama"
},
{
"Name": "QwenDefiant",
"Url": "http://localhost:11434",
"LLM": "qwen3.5-defiant:9b",
"Provider": "Ollama"
}
],
"Prompts": [
{
"Name": "Introduction",
"Template": "تو یک دستیار هوشمند مفید هستی."
},
{
"Name": "DocumentExtraction",
"Template": "متن زیر از یک فایل استخراج شده است. لطفاً آن را تحلیل کن و خروجی را دقیقاً در قالب JSON با کلیدهای زیر برگردان: {\"summary\": \"خلاصه ۳ خطی\", \"key_points\": [\"نکته ۱\", \"نکته ۲\"], \"entities\": {\"نام_اشخاص\": [], \"تاریخ_ها\": []}}. متن: {0}"
}
]
}
qwen3.5-defiant:9b در فیلد LLM باعث میشود کد شما تمیز و خوانا بماند.
🔧 گام ۴: ایجاد سرویس استخراج محتوا (Extraction Service)
ما یک سرویس اختصاصی ایجاد میکنیم که ترکیبی از IFileContentExtractor (برای خواندن فایل) و IChatClient (برای پردازش با Qwen) باشد.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace xAiApi.Interfaces
{
public interface IDocumentExtractionService
{
/// <summary>
/// استخراج و تحلیل هوشمند محتوای یک فایل
/// </summary>
Task<string> ExtractAndAnalyzeAsync(
IFormFile file,
string extractionPromptTemplate,
CancellationToken cancellationToken = default
);
}
}
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using xAiApi.Configurations;
using xAiApi.Constants;
using xAiApi.Extensions;
using xAiApi.Interfaces;
using xAiModels.Constants;
using xAiModels.Extensions;
using xAiService.Interfaces;
using xExceptions.Constants;
namespace xAiApi.Providers
{
public class XDocumentExtractionService : IDocumentExtractionService
{
private readonly IFileContentExtractor _fileExtractor;
private readonly XAiApiConfiguration _configuration;
private readonly ILogger<XDocumentExtractionService> _logger;
public XDocumentExtractionService(
IFileContentExtractor fileExtractor,
XAiApiConfiguration configuration,
ILogger<XDocumentExtractionService> logger)
{
_fileExtractor = fileExtractor;
_configuration = configuration;
_logger = logger;
}
public async Task<string> ExtractAndAnalyzeAsync(
IFormFile file,
string extractionPromptTemplate,
CancellationToken cancellationToken = default)
{
// ۱. اعتبارسنجی فایل
if (file == null || file.Length == 0)
{
XException.InvalidArgs.Throw("فایل نامعتبر است.");
}
// ۲. استخراج متن خام از فایل
string rawText;
using (var stream = file.OpenReadStream())
{
if (_fileExtractor.CanExtract(file.ContentType))
{
rawText = await _fileExtractor.ExtractAsync(stream, file.ContentType, cancellationToken);
}
else
{
XException.InvalidData.Throw($"فرمت فایل {file.ContentType} پشتیبانی نمیشود.");
}
}
if (string.IsNullOrWhiteSpace(rawText))
{
XException.InvalidData.Throw("محتوای استخراج شده از فایل خالی است.");
}
// ۳. آمادهسازی پرامپت نهایی
var finalPrompt = string.Format(extractionPromptTemplate, rawText);
// ۴. دریافت کلاینت مدل QwenDefiant از پیکربندی
var modelDescriptor = _configuration.GetModel("QwenDefiant");
if (!modelDescriptor.IsValid())
{
XException.InvalidConfiguration.Throw("مدل QwenDefiant در پیکربندی یافت نشد.");
}
// ۵. ساخت ChatClient (با استفاده از منطق موجود در XAIServiceBase یا مستقیم)
using var client = CreateChatClient(modelDescriptor);
// ۶. ارسال درخواست به مدل
var history = new[]
{
new ChatMessage(ChatRole.System, "تو یک متخصص استخراج داده از اسناد هستی. فقط خروجی درخواست شده را تولید کن."),
new ChatMessage(ChatRole.User, finalPrompt)
};
var response = await client.GetResponseAsync(history, cancellationToken: cancellationToken);
if (!response.IsValid())
{
XException.ActionFailed.Throw("مدل هوش مصنوعی پاسخی معتبر تولید نکرد.");
}
return response.Text;
}
private IChatClient CreateChatClient(XAiModels.Models.XAiModelDescriptor descriptor)
{
// بازنویسی منطق ساخت کلاینت Ollama بر اساس معماری پروژه
var httpClient = new HttpClient { BaseAddress = new Uri(descriptor.Url), Timeout = Timeout.InfiniteTimeSpan };
var ollamaClient = new OllamaSharp.OllamaApiClient(httpClient, descriptor.LLM);
return new ChatClientBuilder(ollamaClient)
.UseFunctionInvocation()
.Build();
}
}
}
🎮 گام ۵: ایجاد Controller اختصاصی
یک endpoint جدید برای دریافت فایل و بازگرداندن محتوای تحلیلشده ایجاد میکنیم.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using xCommons.Configurations;
using xCommons.Providers;
using xIdentityService.Interfaces;
using xAiApi.Interfaces;
using xAiApi.Extensions;
namespace xAiApi.Controllers
{
[Authorize]
[Route("api/[controller]")]
public class DocumentExtractionController : XBaseIdentityApiV1Controller
{
private readonly IDocumentExtractionService _extractionService;
private readonly XAiApiConfiguration _configuration;
public DocumentExtractionController(
ILogger<DocumentExtractionController> logger,
XAppConfiguration appConfiguration,
XValidationProvider validationProvider,
IXIdentityProvider identityProvider,
IDocumentExtractionService extractionService,
XAiApiConfiguration configuration)
: base(logger, appConfiguration, validationProvider, identityProvider)
{
_extractionService = extractionService;
_configuration = configuration;
}
/// <summary>
/// آپلود فایل و استخراج هوشمند محتوا با مدل Qwen3.5
/// </summary>
[HttpPost("Extract")]
[Consumes("multipart/form-data")]
public async Task<ActionResult<object>> Extract(
[FromForm] IFormFile file,
[FromForm] string promptName = "DocumentExtraction",
CancellationToken cancellationToken = default)
{
try
{
ValidationProvider.NotNull(file, nameof(file));
// دریافت الگوی پرامپت از پیکربندی
var promptTemplate = _configuration.GetPrompt(promptName);
if (string.IsNullOrWhiteSpace(promptTemplate))
{
promptTemplate = "متن زیر را تحلیل و خلاصه کن: {0}";
}
// فراخوانی سرویس استخراج
var result = await _extractionService.ExtractAndAnalyzeAsync(
file: file,
extractionPromptTemplate: promptTemplate,
cancellationToken: cancellationToken
);
return Ok(new { success = true, data = result });
}
catch (Exception ex)
{
return GetExceptionActionResult(ex);
}
}
}
}
🔗 گام ۶: ثبت وابستگیها (Dependency Injection)
سرویس جدید را در متد ConfigureServices فایل Startup.cs ثبت کنید.
public void ConfigureServices(IServiceCollection services)
{
// ... (ثبتهای قبلی)
// ثبت Extractor های فایل (اگر قبلاً ثبت نشدهاند)
services.AddSingleton<IFileContentExtractor, PlainTextContentExtractor>();
services.AddSingleton<IFileContentExtractor, PdfContentExtractor>();
services.AddSingleton<IFileContentExtractor, CompositeFileContentExtractor>();
// ✅ ثبت سرویس جدید استخراج محتوا
services.AddScoped<IDocumentExtractionService, XDocumentExtractionService>();
// ... (بقیه کدها)
}
🔄 گام ۷: جریان کامل پردازش
نمونه درخواست (cURL):
curl -X POST "http://localhost:5000/api/DocumentExtraction/Extract" \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "file=@/path/to/document.pdf" \ -F "promptName=DocumentExtraction"
💡 گام ۸: نکات کلیدی و مهندسی پرامپت برای مدلهای GGUF
🎯 پرامپتنویسی دقیق
مدلهای GGUF محلی به دستورالعملهای شفاف بسیار خوب پاسخ میدهند. در appsettings.json حتماً قالب خروجی (مثلاً JSON) را به صراحت مشخص کنید.
⚡ مدیریت Context Window
در Modelfile مقدار num_ctx را بر اساس حجم فایلهای شما تنظیم کنید (مثلاً 8192 یا 16384). اگر فایل بزرگ است، آن را به قطعات (Chunks) تقسیم کنید.
🛡️ مدیریت خطا
همیشه احتمال خطای OOM (کمبود حافظه RAM/VRAM) در مدلهای 9B را در نظر بگیرید. لاگهای Ollama را برای پایش مصرف حافظه بررسی کنید.
🖼️ پشتیبانی از تصویر (Vision)
اگر این نسخه خاص از Qwen از ورودی تصویر پشتیبانی کند، میتوانید در XDocumentExtractionService به جای متن خام، فایل تصویر را به DataContent تبدیل و ارسال کنید.
XAIServiceBase، یک ماژول کاملاً ایزوله و قدرتمند برای استخراج محتوا با مدلهای محلی GGUF ایجاد کردهاید که کاملاً با معماری ماژولار شرکت فن آوران ساحر علم همخوانی دارد.