📑 فهرست مطالب
🎯 گام ۱: استراتژی یکپارچهسازی
به جای مدیریت مستقیم IFormFile در لایه سرویس هوش مصنوعی، از معماری تمیز (Clean Architecture) پیروی میکنیم:
۱. لایه ارائه (Controller)
- دریافت
IFormFileCollection - فراخوانی
IXFileProvider.Upload - دریافت لیست
XFileDto
۲. لایه سرویس (XAiServiceBase)
- دریافت
IEnumerable<XFileDto> - فراخوانی
IXFileProvider.GetFileDescriptor - خواندن Stream و تبدیل به
AIContent
۳. لایه داده (Metadata)
- ذخیره
FileIdدر فیلدMetaDatasپیام - حفظ رابطه بدون نیاز به جدول Join جدید
🎮 گام ۲: بهروزرسانی Controller برای آپلود فایل
ابتدا باید فایلها را از طریق XFileProvider آپلود کنیم تا XFileDto دریافت شود.
// ۱. افزودن وابستگی به Constructor
private readonly IXFileProvider _fileProvider;
protected XAiServiceControllerBase(
// ... پارامترهای قبلی
IXFileProvider fileProvider // ✅ جدید
) : base(...)
{
// ...
_fileProvider = fileProvider;
}
// ۲. اصلاح متد Ask
[HttpPost("Ask")]
[Consumes("multipart/form-data")]
public async Task<ActionResult<string>> Ask(
[FromForm] XAiResponseRequest request,
[FromForm] IFormFileCollection files,
CancellationToken cancellationToken = default
)
{
try
{
if (!request.IsValid()) XException.InvalidArgs.Throw();
var userInfo = await GetUserInfo();
var connectionId = GetConnectionId();
// ✅ آپلود فایلها از طریق ماژول xFileService
IEnumerable<XFileDto> uploadedFiles = null;
if (files != null && files.Any())
{
uploadedFiles = await _fileProvider.Upload(
files: files,
userInfo: userInfo,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
// ✅ ارسال XFileDto به سرویس هوش مصنوعی
var result = await aiService.AskAsync(
prompt: request.Prompt,
ownerId: userInfo.UserId,
projectId: request.ProjectId,
conversationId: request.ConversationId,
connectionId: connectionId,
attachedFiles: uploadedFiles, // ✅ تغییر نوع پارامتر
cancellationToken: cancellationToken
);
return Ok(result);
}
catch (Exception ex)
{
return GetExceptionActionResult(ex);
}
}
AskStream نیز اعمال شود.
⚙️ گام ۳: تزریق IXFileProvider به XAiServiceBase
// ۱. افزودن فیلد و تزریق در Constructor
private readonly IXFileProvider _fileProvider;
protected XAIServiceBase(
IXAiDataProvider dataProvider,
ILogger<XAIServiceBase> logger,
XAiApiConfiguration configuration,
XValidationProvider validationProvider,
IXFileProvider fileProvider, // ✅ جدید
string model = null
)
{
this.dataProvider = dataProvider;
this.logger = logger;
this.configuration = configuration;
this.validationProvider = validationProvider;
this._fileProvider = fileProvider; // ✅ مقداردهی
Descriptor = configuration.GetModel(model);
Options = new ChatOptions();
}
// ۲. بهروزرسانی امضای متد AskAsync در Interface و Implementation
public async Task<XAiMessageDto> AskAsync(
string prompt,
string ownerId,
Guid projectId,
Guid conversationId,
string connectionId = null,
IEnumerable<XFileDto> attachedFiles = null, // ✅ تغییر از IFormFileCollection
CancellationToken cancellationToken = default
)
{
// ... (کدهای اعتبارسنجی و دریافت Project/Conversation)
// ✅ پردازش فایلهای ضمیمه
var fileContents = await ProcessAttachedFilesAsync(attachedFiles, cancellationToken);
// ... (ساخت promptMessage)
// ✅ ذخیره ارجاع فایلها در MetaDatas پیام
if (attachedFiles != null && attachedFiles.Any())
{
var fileIds = attachedFiles.Select(f => f.Id).ToList();
promptMessage.MetaDatas = new Dictionary<string, object>
{
{ "AttachedFileIds", fileIds }
}.ToJSON();
}
promptMessage = await dataProvider.AddMessage(...);
// ✅ الحاق محتوا به ChatMessage
var promptChatMessage = promptMessage.ToChatMessages(fileContents);
var answer = await AskLLMAsync(history: history, prompt: promptChatMessage, cancellationToken: cancellationToken);
// ... (ذخیره پاسخ و بازگشت نتیجه)
}
🔍 گام ۴: پیادهسازی منطق استخراج محتوا از فایل
این متد کمکی درون XAiServiceBase مسئول خواندن فایل از طریق XFileProvider و تبدیل آن به فرمت قابل فهم برای LLM است.
/// <summary>
/// پردازش فایلهای ضمیمه و تبدیل به AIContent
/// </summary>
private async Task<IList<AIContent>> ProcessAttachedFilesAsync(
IEnumerable<XFileDto> files,
CancellationToken cancellationToken = default
)
{
var contents = new List<AIContent>();
if (files == null || !files.Any())
{
return contents;
}
foreach (var file in files)
{
try
{
// ✅ دریافت استریم فایل از ماژول xFileService
var descriptor = await _fileProvider.GetFileDescriptor(
id: file.Id,
cancellationToken: cancellationToken
);
if (descriptor == null || descriptor.Stream == null)
{
logger.LogWarning("فایل {FileName} یافت نشد یا قابل خواندن نیست.", file.FileName);
continue;
}
// ✅ تشخیص نوع فایل و پردازش مناسب
if (file.Type == XFileType.Image || descriptor.MIMEType.StartsWith("image/"))
{
// برای مدلهای Vision: ارسال به صورت DataContent
using var memoryStream = new MemoryStream();
await descriptor.Stream.CopyToAsync(memoryStream, cancellationToken);
contents.Add(new DataContent(memoryStream.ToArray(), descriptor.MIMEType));
}
else
{
// برای فایلهای متنی: خواندن محتوا و الحاق به Prompt
using var reader = new StreamReader(descriptor.Stream);
var textContent = await reader.ReadToEndAsync(cancellationToken);
// قالببندی برای درک بهتر مدل از منبع متن
var formattedText = $"[File: {file.FileName} (Type: {descriptor.MIMEType})]\n{textContent}\n[/File]";
contents.Add(new TextContent(formattedText));
}
}
catch (Exception ex)
{
logger.LogError(ex, "خطا در پردازش فایل ضمیمه: {FileName}", file.FileName);
}
}
return contents;
}
GetFileDescriptor، ماژول هوش مصنوعی نیازی به دانستن جزئیات سیستم فایل (File System) ندارد و کاملاً از xFileService انتزاع یافته است.
🔌 گام ۵: بهروزرسانی Extension مدلها
متد ToChatMessages باید بتواند محتوای استخراج شده از فایلها را در کنار متن اصلی پیام قرار دهد.
public static ChatMessage ToChatMessages(
this XAiMessageDto source,
IList<AIContent> additionalContents = null // ✅ پارامتر جدید
)
{
ChatMessage result = null;
if (!source.IsNullOrDefault())
{
var contents = new List<AIContent>();
// ۱. افزودن متن اصلی پیام (Prompt کاربر)
if (!string.IsNullOrWhiteSpace(source.Content))
{
contents.Add(new TextContent(source.Content));
}
// ۲. ✅ افزودن محتوای استخراج شده از فایلها
if (additionalContents != null && additionalContents.Any())
{
contents.AddRange(additionalContents);
}
result = new ChatMessage
{
AuthorName = source.Role == XAiChatRole.User && !source.Owner.IsNullOrDefault()
? source.Owner.GetFullname()
: string.Empty,
Role = source.Role.ToChatRole(),
MessageId = source.Id.ToString(),
Contents = contents // ✅ لیست ترکیبی از متن و فایل
};
}
return result;
}
🔗 گام ۶: ثبت وابستگیها (Dependency Injection)
اطمینان حاصل کنید که IXFileProvider در کانتینر DI ثبت شده است (که بر اساس فایلهای ارائه شده، قبلاً در xFileService.DI.XDIHelperExtension انجام شده است). فقط باید اطمینان حاصل کنیم که در xAiApi قابل تزریق است.
public void ConfigureServices(IServiceCollection services)
{
// ... (سایر ثبتها)
// ✅ اطمینان از ثبت سرویس فایل (اگر قبلاً در ماژول xFileService ثبت نشده، اینجا فراخوانی شود)
// services.AddXFileService<XAiApiDbContext>(xDataService.Constants.XRepositoryType.EF);
// ✅ بهروزرسانی ثبت سرویسهای AI برای تزریق IXFileProvider
// نکته: چون XAIServiceBase کلاس پایه است، باید در کلاسهای مشتق شده (مثل XDefaultAiService) تزریق شود.
// مثال برای XDefaultAiService:
// services.AddScoped<IXDefaultAiService>(sp => new XDefaultAiService(
// sp.GetRequiredService<IXAiDataProvider>(),
// sp.GetRequiredService<ILogger<XDefaultAiService>>(),
// sp.GetRequiredService<XAiApiConfiguration>(),
// sp.GetRequiredService<XValidationProvider>(),
// sp.GetRequiredService<IXFileProvider>() // ✅ تزریق جدید
// ));
}
XAiServiceBase را مستقیماً ثبت نمیکنید و از کلاسهای مشتق شده استفاده میکنید، باید Constructor آن کلاسها را نیز برای پذیرش IXFileProvider و پاس دادن آن به base(...) بهروزرسانی کنید.
🔄 گام ۷: جریان کامل پردازش
📋 گام ۸: خلاصه تغییرات
| ردیف | فایل / ماژول | نوع تغییر | توضیح |
|---|---|---|---|
| ۱ | xAiApi/Controllers/XAiServiceControllerBase.cs |
MODIFY | افزودن IXFileProvider و فراخوانی Upload قبل از سرویس AI |
| ۲ | xAiApi/Interfaces/IXAiServiceBase.cs |
MODIFY | تغییر پارامتر files از IFormFileCollection به IEnumerable<XFileDto> |
| ۳ | xAiApi/Providers/XAIServiceBase.cs |
MODIFY | تزریق IXFileProvider و افزودن متد ProcessAttachedFilesAsync |
| ۴ | xAiModels/Extensions/XAiModelsExtensions.cs |
MODIFY | پشتیبانی ToChatMessages از additionalContents |
| ۵ | xAiApi/Providers/XDefaultAiService.cs (و سایر مشتقها) |
MODIFY | بهروزرسانی Constructor برای پاس دادن IXFileProvider به کلاس پایه |
- 🛡️ جداسازی مسئولیتها: ماژول AI دیگر درگیر آپلود یا مدیریت فایل فیزیکی نیست.
- ♻️ استفاده مجدد: از تمام قابلیتهای
xFileService(مانند Thumbnail، References، و Storage) بهره میبریم. - 🔗 ردیابیپذیری: با ذخیره
FileIdدرMetaDatas، همیشه میتوانیم بفهمیم کدام فایلها به کدام پیام متصل بودهاند. - 🎨 پشتیبانی چندوجهی (Multi-modal): آمادهسازی برای ارسال تصاویر به صورت
DataContentبه مدلهای Vision.