📑 فهرست مطالب
🔍 گام ۱: تحلیل دقیق ریشه خطا
پیغام خطای DI به وضوح یک حلقه وابستگی (Circular Dependency) را نشان میدهد:
XVisionFileContentExtractorدر constructor خود بهIXDefaultAIOCRServiceنیاز دارد.XDefaultAIOCRServiceدر constructor خود بهIXFileContentExtractorنیاز دارد.- اما
IXFileContentExtractorهمان کامپوزیتی است که شاملXVisionFileContentExtractorمیباشد! - این یک حلقه بینهایت ایجاد میکند و DI Container قادر به ساخت هیچیک از این سرویسها نیست.
🎯 گام ۲: شناسایی علت اصلی
با بررسی دقیق کد XDefaultAIOCRService، یک نکته کلیدی کشف شد:
❌ پارامتر غیرضروری در Constructor
در constructor کلاس XDefaultAIOCRService، پارامتر IXFileContentExtractor fileContentExtractor دریافت میشود، اما در هیچ جای کلاس از آن استفاده نمیشود!
✅ سرویس OCR مستقل است
سرویس OCR فقط نیاز به ارتباط با مدل زبانی (LLM) دارد و به IXFileContentExtractor نیازی ندارد. این پارامتر به اشتباه از نسخه قبلی باقی مانده است.
کد مشکلدار فعلی:
public XDefaultAIOCRService(
XAiApiConfiguration configuration,
ILogger<XDefaultAIOCRService> logger,
IXFileContentExtractor fileContentExtractor, // ❌ پارامتر غیرضروری
string model = null,
string prompt = null
)
{
// ... هیچ استفادهای از fileContentExtractor در body کلاس نیست
}
💡 گام ۳: راهحل اصلاحی
برای شکستن حلقه وابستگی، کافی است پارامتر غیرضروری IXFileContentExtractor را از constructor کلاس XDefaultAIOCRService حذف کنیم.
🛠️ گام ۴: اصلاح XDefaultAIOCRService
کد اصلاحشده:
using System;
using OpenAI;
using OllamaSharp;
using System.Net.Http;
using System.Threading;
using xAiModels.Models;
using xAiApi.Constants;
using xAiApi.Interfaces;
using xAiApi.Extensions;
using System.ClientModel;
using xCommons.Extensions;
using xAiModels.Extensions;
using xAiApi.Configurations;
using xExceptions.Constants;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using System.ClientModel.Primitives;
using System.Runtime.CompilerServices;
namespace xAiApi.Providers
{
public class XDefaultAIOCRService : IXDefaultAIOCRService
{
/// <summary>
/// Chat Options ...
/// </summary>
public ChatOptions Options { get; }
/// <summary>
/// Prompt ...
/// </summary>
public string Prompt { get; }
/// <summary>
/// Descriptor of Models which used in Service ...
/// </summary>
public XAiModelDescriptor Descriptor { get; }
public XDefaultAIOCRService(
XAiApiConfiguration configuration,
ILogger<XDefaultAIOCRService> logger,
// ✅ پارامتر IXFileContentExtractor حذف شد
string model = null,
string prompt = null
)
{
//
// Prepare Model Descriptor ...
Descriptor = configuration.GetModel(model);
if (!Descriptor.IsValid())
{
XException.InvalidConfiguration.Throw();
}
//
Prompt = prompt;
if (Prompt.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Chat Options based On Tools ...
Options = new ChatOptions();
}
//
#region OCR ...
/// <summary>
/// Request for Doing OCR on Given Data Contents ...
/// </summary>
public virtual async Task<string> RequestOCRAsync(
IList<ChatMessage> messages,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (!messages.HasChild())
{
XException.InvalidArgs.Throw();
}
//
using var client = GetClient();
//
// Preparing Extraction Prompt Message ...
var pMessage = new ChatMessage(
ChatRole.System,
Prompt
);
//
messages = [pMessage, .. messages];
//
var response = await client.GetResponseAsync(
messages: messages,
cancellationToken: cancellationToken
);
//
// Validate Response ...
if (!response.IsValid())
{
//
// Dispose Client ...
client.Dispose();
XException.ActionFailed.Throw();
}
//
// Retrieve Response Text ...
var result = response.Text;
//
return result;
}
/// <summary>
/// Request for Doing OCR on Given Data Contents as Stream ...
/// </summary>
public virtual async IAsyncEnumerable<string> RequestOCRAsEnumerable(
IList<ChatMessage> messages,
[EnumeratorCancellation]
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (!messages.HasChild())
{
XException.InvalidArgs.Throw();
}
//
using var client = GetClient();
//
// Preparing Extraction Prompt Message ...
var pMessage = new ChatMessage(
ChatRole.System,
Prompt
);
//
messages = [pMessage, .. messages];
//
var enumerable = client.GetStreamingResponseAsync(
options: null,
messages: messages
);
//
await foreach (var res in enumerable)
{
//
// Cancellation Token ...
if (cancellationToken.IsCancellationRequested)
{
yield break;
}
//
yield return res.Text;
}
}
#endregion
//
#region Preaprations ...
/// <summary>
/// Get LLM Client instance for Communicating with LLM ...
/// </summary>
public virtual IChatClient GetClient()
{
//
// Try to Initialize LLm ...
var model = Descriptor.LLM;
var apiKey = Descriptor.ApiKey;
var url = new Uri(Descriptor.Url);
var httpClient = GetHttpClient(Descriptor.Url);
//
IChatClient result = null;
switch (Descriptor.Provider)
{
//
case XAiModelProviderType.Ollama:
//
var ollamaClient = new OllamaApiClient(httpClient, model);
result =
new ChatClientBuilder(ollamaClient)
.UseFunctionInvocation()
.Build();
break;
//
case XAiModelProviderType.OpenAI:
//
var openAiClient = new OpenAIClient(
new ApiKeyCredential(apiKey.IsNullOrEmpty() ? XAiApiConstants.XOpenAINoKey : apiKey),
new OpenAIClientOptions
{
Endpoint = url,
Transport = new HttpClientPipelineTransport(httpClient)
}
);
result =
new ChatClientBuilder(
openAiClient
.GetChatClient(model)
.AsIChatClient())
.UseFunctionInvocation()
.Build();
break;
//
case XAiModelProviderType.DeepSeek:
break;
//
case XAiModelProviderType.HuggingFace:
break;
//
default:
break;
}
//
if (result.IsNullOrDefault())
{
XException.InvalidData.Throw();
}
//
return result;
}
/// <summary>
/// Create Custom HttpClient for Communicating with LLM API ...
/// </summary>
public virtual HttpClient GetHttpClient(string url = null)
{
//
var result = new HttpClient
{
//
// Disable timeout completely (not recommended for production)
Timeout = Timeout.InfiniteTimeSpan,
//
BaseAddress = url.IsNullOrEmpty()
? null
: new Uri(url),
};
//
return result;
}
#endregion
}
}
🔌 گام ۵: بررسی Startup.cs
با توجه به تغییرات فوق، ثبت سرویسها در Startup.cs بدون هیچ تغییری به درستی کار خواهد کرد. اما برای اطمینان، ترتیب ثبت سرویسها را بررسی میکنیم:
public void ConfigureServices(IServiceCollection services)
{
// ... (ثبتهای قبلی)
// ✅ ثبت File Content Extractors (به ترتیب صحیح)
services.AddSingleton<IXFileContentExtractor, XPdfFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XDocxFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XImageFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XAudioFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XExcelFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XPlainTextFileContentExtractor>();
// ✅ ثبت Vision Extractor (وابسته به IXDefaultAIOCRService)
services.AddSingleton<IXFileContentExtractor, XVisionFileContentExtractor>();
// ✅ ثبت کامپوزیت اصلی (لیست بالا را در Constructor دریافت میکند)
services.AddSingleton<IXFileContentExtractor, XFileContentExtractor>();
// ✅ ثبت سرویس OCR (دیگر به IXFileContentExtractor وابسته نیست)
services.AddScoped<IXDefaultAIOCRService, XDefaultAIOCRService>();
// ✅ ثبت سایر سرویسهای AI
services.AddScoped<IXDefaultAiService, XDefaultAiService>();
services.AddScoped<IXDefaultEmbeddingService, XDefaultEmbeddingService>();
services.AddScoped<IXDefaultThinkingAiService, XDefaultThinkingAiService>();
}
✅ گام ۶: تأیید رفع خطا
خلاصه تغییرات:
| فایل | تغییر | دلیل |
|---|---|---|
XDefaultAIOCRService.cs |
REMOVE حذف پارامتر IXFileContentExtractor |
پارامتر غیرضروری بود و باعث Circular Dependency میشد |
Startup.cs |
NO CHANGE بدون تغییر | ثبت سرویسها به درستی انجام شده است |
مزایای این اصلاح:
🚫 حذف Circular Dependency
- حلقه وابستگی کاملاً شکسته شد
- DI Container میتواند تمام سرویسها را بسازد
⚡ افزایش Performance
- سرویس OCR دیگر بار اضافی ندارد
- تزریق وابستگیهای کمتر = ساخت سریعتر
🎯 اصل تکوظیفهای (SRP)
- سرویس OCR فقط مسئول ارتباط با LLM است
- وابستگیهای غیرضروری حذف شدند
🧪 قابلیت تستپذیری
- Unit Test سادهتر شد
- Mock کردن وابستگیها آسانتر است
با حذف پارامتر غیرضروری IXFileContentExtractor از constructor کلاس XDefaultAIOCRService، خطای Circular Dependency کاملاً رفع میشود و پروژه بدون مشکل اجرا خواهد شد.
پس از اعمال این تغییر، پروژه را rebuild کنید و خطای System.AggregateException دیگر ظاهر نخواهد شد.