Files
2026-10-03 18:04:36 +03:30

616 lines
26 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>رفع خطای Circular Dependency در XDefaultAIOCRService - فن آوران ساحر علم</title>
<style>
:root {
--primary: #1e3a8a;
--secondary: #3b82f6;
--accent: #f59e0b;
--success: #10b981;
--danger: #ef4444;
--warning: #f97316;
--bg-light: #f8fafc;
--bg-code: #1e293b;
--text-dark: #0f172a;
--text-muted: #64748b;
--border: #e2e8f0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Tahoma', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 100%);
color: var(--text-dark);
line-height: 1.8;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 { font-size: 2.1em; margin-bottom: 10px; }
.header .subtitle { font-size: 1.1em; opacity: 0.95; }
.meta-bar {
display: flex;
justify-content: space-between;
background: var(--bg-light);
padding: 15px 30px;
border-bottom: 2px solid var(--border);
flex-wrap: wrap;
gap: 15px;
}
.meta-item { display: flex; align-items: center; gap: 8px; font-size: 0.9em; color: var(--text-muted); }
.meta-item strong { color: var(--primary); }
.content { padding: 40px; }
.section {
margin-bottom: 35px;
padding: 25px;
background: var(--bg-light);
border-radius: 12px;
border-right: 5px solid var(--secondary);
}
.section h2 {
color: var(--primary);
font-size: 1.5em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.section h3 { color: var(--secondary); font-size: 1.2em; margin: 20px 0 12px; }
pre {
background: var(--bg-code);
color: #e2e8f0;
padding: 18px;
border-radius: 8px;
overflow-x: auto;
direction: ltr;
text-align: left;
font-family: 'Consolas', monospace;
font-size: 0.85em;
margin: 15px 0;
border-right: 4px solid var(--accent);
}
code {
background: #fef3c7;
color: #92400e;
padding: 2px 8px;
border-radius: 4px;
font-family: 'Consolas', monospace;
font-size: 0.9em;
direction: ltr;
display: inline-block;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
background: white;
border-radius: 8px;
overflow: hidden;
}
th { background: var(--primary); color: white; padding: 12px; text-align: right; }
td { padding: 12px; border-bottom: 1px solid var(--border); }
tr:hover { background: var(--bg-light); }
.alert { padding: 15px 20px; border-radius: 8px; margin: 15px 0; border-right: 4px solid; }
.alert-info { background: #dbeafe; border-color: var(--secondary); color: #1e40af; }
.alert-success { background: #d1fae5; border-color: var(--success); color: #065f46; }
.alert-danger { background: #fee2e2; border-color: var(--danger); color: #991b1b; }
.alert-warning { background: #fef3c7; border-color: var(--accent); color: #92400e; }
.footer { background: var(--primary); color: white; padding: 25px; text-align: center; }
.footer p { margin: 5px 0; }
.highlight { background: linear-gradient(120deg, #fef3c7 0%, #fef3c7 100%); padding: 2px 6px; border-radius: 4px; font-weight: bold; }
.toc { background: white; padding: 20px; border-radius: 10px; margin-bottom: 25px; border: 2px solid var(--border); }
.toc h3 { color: var(--primary); margin-bottom: 15px; }
.toc ol { padding-right: 25px; }
.toc li { padding: 6px 0; }
.toc a { color: var(--secondary); text-decoration: none; }
.file-change { background: #f0f9ff; border-right: 4px solid var(--secondary); padding: 15px; margin: 10px 0; border-radius: 8px; }
.file-change .path { font-family: 'Consolas', monospace; color: var(--primary); font-weight: bold; direction: ltr; display: inline-block; }
.badge-modify { background: var(--warning); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.badge-remove { background: var(--danger); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.arch-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin: 20px 0; }
.arch-card { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); border-top: 4px solid var(--secondary); }
.arch-card h4 { color: var(--primary); margin-bottom: 12px; }
.arch-card ul { list-style: none; padding-right: 0; }
.arch-card li { padding: 6px 0; padding-right: 20px; position: relative; }
.arch-card li::before { content: '▸'; position: absolute; right: 0; color: var(--accent); font-weight: bold; }
.diff-old { background: #fee2e2; color: #991b1b; padding: 2px 4px; border-radius: 3px; text-decoration: line-through; }
.diff-new { background: #d1fae5; color: #065f46; padding: 2px 4px; border-radius: 3px; }
.chain-diagram { background: white; padding: 25px; border-radius: 10px; margin: 20px 0; text-align: center; }
.chain-step { display: inline-block; background: var(--danger); color: white; padding: 10px 18px; border-radius: 8px; margin: 5px; font-size: 0.88em; }
.chain-step.fixed { background: var(--success); }
.chain-arrow { display: inline-block; color: var(--accent); font-size: 1.5em; margin: 0 8px; vertical-align: middle; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔗 رفع خطای Circular Dependency</h1>
<div class="subtitle">تحلیل و رفع حلقه وابستگی بین XVisionFileContentExtractor و XDefaultAIOCRService</div>
</div>
<div class="meta-bar">
<div class="meta-item">👨‍💻 <strong>توسعه‌دهنده:</strong> هادی خزاعی اصل</div>
<div class="meta-item">🏢 <strong>شرکت:</strong> فن آوران ساحر علم</div>
<div class="meta-item">📅 <strong>تاریخ:</strong> شنبه ۱۲ مهر ۱۴۰۵</div>
<div class="meta-item">📦 <strong>پروژه:</strong> xAiApi</div>
</div>
<div class="content">
<div class="toc">
<h3>📑 فهرست مطالب</h3>
<ol>
<li><a href="#analysis">تحلیل دقیق ریشه خطا</a></li>
<li><a href="#root-cause">گام ۱: شناسایی علت اصلی</a></li>
<li><a href="#solution">گام ۲: راه‌حل اصلاحی</a></li>
<li><a href="#step1">گام ۳: اصلاح XDefaultAIOCRService</a></li>
<li><a href="#step2">گام ۴: بررسی Startup.cs</a></li>
<li><a href="#verification">گام ۵: تأیید رفع خطا</a></li>
</ol>
</div>
<!-- Section 1: Analysis -->
<div class="section" id="analysis">
<h2>🔍 گام ۱: تحلیل دقیق ریشه خطا</h2>
<p>پیغام خطای DI به وضوح یک <strong>حلقه وابستگی (Circular Dependency)</strong> را نشان می‌دهد:</p>
<div class="chain-diagram">
<span class="chain-step">IXFileContentExtractor</span>
<span class="chain-arrow">→</span>
<span class="chain-step">XVisionFileContentExtractor</span>
<span class="chain-arrow">→</span>
<span class="chain-step">IXDefaultAIOCRService</span>
<span class="chain-arrow">→</span>
<span class="chain-step">XDefaultAIOCRService</span>
<span class="chain-arrow">→</span>
<span class="chain-step">IXFileContentExtractor ❌</span>
</div>
<div class="alert alert-danger">
<strong>⚠️ تحلیل زنجیره:</strong>
<ol style="padding-right: 25px; margin-top: 10px;">
<li><code>XVisionFileContentExtractor</code> در constructor خود به <code>IXDefaultAIOCRService</code> نیاز دارد.</li>
<li><code>XDefaultAIOCRService</code> در constructor خود به <code>IXFileContentExtractor</code> نیاز دارد.</li>
<li>اما <code>IXFileContentExtractor</code> همان کامپوزیتی است که شامل <code>XVisionFileContentExtractor</code> می‌باشد!</li>
<li>این یک <strong>حلقه بی‌نهایت</strong> ایجاد می‌کند و DI Container قادر به ساخت هیچ‌یک از این سرویس‌ها نیست.</li>
</ol>
</div>
</div>
<!-- Section 2: Root Cause -->
<div class="section" id="root-cause">
<h2>🎯 گام ۲: شناسایی علت اصلی</h2>
<p>با بررسی دقیق کد <code>XDefaultAIOCRService</code>، یک نکته کلیدی کشف شد:</p>
<div class="arch-grid">
<div class="arch-card">
<h4>❌ پارامتر غیرضروری در Constructor</h4>
<p>در constructor کلاس <code>XDefaultAIOCRService</code>، پارامتر <code>IXFileContentExtractor fileContentExtractor</code> دریافت می‌شود، اما <strong>در هیچ جای کلاس از آن استفاده نمی‌شود!</strong></p>
</div>
<div class="arch-card">
<h4>✅ سرویس OCR مستقل است</h4>
<p>سرویس OCR فقط نیاز به ارتباط با مدل زبانی (LLM) دارد و به <code>IXFileContentExtractor</code> نیازی ندارد. این پارامتر به اشتباه از نسخه قبلی باقی مانده است.</p>
</div>
</div>
<h3>کد مشکل‌دار فعلی:</h3>
<pre>public XDefaultAIOCRService(
XAiApiConfiguration configuration,
ILogger&lt;XDefaultAIOCRService&gt; logger,
<span class="diff-old">IXFileContentExtractor fileContentExtractor, // ❌ پارامتر غیرضروری</span>
string model = null,
string prompt = null
)
{
// ... هیچ استفاده‌ای از fileContentExtractor در body کلاس نیست
}</pre>
</div>
<!-- Section 3: Solution -->
<div class="section" id="solution">
<h2>💡 گام ۳: راه‌حل اصلاحی</h2>
<p>برای شکستن حلقه وابستگی، کافی است <strong>پارامتر غیرضروری <code>IXFileContentExtractor</code> را از constructor کلاس <code>XDefaultAIOCRService</code> حذف کنیم</strong>.</p>
<div class="chain-diagram">
<span class="chain-step fixed">IXFileContentExtractor</span>
<span class="chain-arrow">→</span>
<span class="chain-step fixed">XVisionFileContentExtractor</span>
<span class="chain-arrow">→</span>
<span class="chain-step fixed">IXDefaultAIOCRService</span>
<span class="chain-arrow">→</span>
<span class="chain-step fixed">XDefaultAIOCRService ✅</span>
</div>
<div class="alert alert-success">
<strong>✅ نتیجه:</strong> با حذف این پارامتر، زنجیره وابستگی کاملاً شکسته می‌شود و DI Container می‌تواند تمام سرویس‌ها را بدون مشکل مقداردهی اولیه کند.
</div>
</div>
<!-- Section 4: Step 1 -->
<div class="section" id="step1">
<h2>🛠️ گام ۴: اصلاح XDefaultAIOCRService</h2>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Providers/XDefaultAIOCRService.cs</span>
</div>
<h3>کد اصلاح‌شده:</h3>
<pre>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
{
/// &lt;summary&gt;
/// Chat Options ...
/// &lt;/summary&gt;
public ChatOptions Options { get; }
/// &lt;summary&gt;
/// Prompt ...
/// &lt;/summary&gt;
public string Prompt { get; }
/// &lt;summary&gt;
/// Descriptor of Models which used in Service ...
/// &lt;/summary&gt;
public XAiModelDescriptor Descriptor { get; }
public XDefaultAIOCRService(
XAiApiConfiguration configuration,
ILogger&lt;XDefaultAIOCRService&gt; logger,
<span class="diff-new">// ✅ پارامتر IXFileContentExtractor حذف شد</span>
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 ...
/// &lt;summary&gt;
/// Request for Doing OCR on Given Data Contents ...
/// &lt;/summary&gt;
public virtual async Task&lt;string&gt; RequestOCRAsync(
IList&lt;ChatMessage&gt; 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;
}
/// &lt;summary&gt;
/// Request for Doing OCR on Given Data Contents as Stream ...
/// &lt;/summary&gt;
public virtual async IAsyncEnumerable&lt;string&gt; RequestOCRAsEnumerable(
IList&lt;ChatMessage&gt; 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 ...
/// &lt;summary&gt;
/// Get LLM Client instance for Communicating with LLM ...
/// &lt;/summary&gt;
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;
}
/// &lt;summary&gt;
/// Create Custom HttpClient for Communicating with LLM API ...
/// &lt;/summary&gt;
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
}
}</pre>
</div>
<!-- Section 5: Step 2 -->
<div class="section" id="step2">
<h2>🔌 گام ۵: بررسی Startup.cs</h2>
<p>با توجه به تغییرات فوق، ثبت سرویس‌ها در <code>Startup.cs</code> بدون هیچ تغییری به درستی کار خواهد کرد. اما برای اطمینان، ترتیب ثبت سرویس‌ها را بررسی می‌کنیم:</p>
<div class="file-change">
<span class="badge-modify">CHECK</span>
<span class="path">xAiApi/Startup.cs (متد ConfigureServices)</span>
</div>
<pre>public void ConfigureServices(IServiceCollection services)
{
// ... (ثبت‌های قبلی)
// ✅ ثبت File Content Extractors (به ترتیب صحیح)
services.AddSingleton&lt;IXFileContentExtractor, XPdfFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XDocxFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XImageFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XAudioFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XExcelFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XPlainTextFileContentExtractor&gt;();
// ✅ ثبت Vision Extractor (وابسته به IXDefaultAIOCRService)
services.AddSingleton&lt;IXFileContentExtractor, XVisionFileContentExtractor&gt;();
// ✅ ثبت کامپوزیت اصلی (لیست بالا را در Constructor دریافت می‌کند)
services.AddSingleton&lt;IXFileContentExtractor, XFileContentExtractor&gt;();
// ✅ ثبت سرویس OCR (دیگر به IXFileContentExtractor وابسته نیست)
services.AddScoped&lt;IXDefaultAIOCRService, XDefaultAIOCRService&gt;();
// ✅ ثبت سایر سرویس‌های AI
services.AddScoped&lt;IXDefaultAiService, XDefaultAiService&gt;();
services.AddScoped&lt;IXDefaultEmbeddingService, XDefaultEmbeddingService&gt;();
services.AddScoped&lt;IXDefaultThinkingAiService, XDefaultThinkingAiService&gt;();
}</pre>
<div class="alert alert-info">
<strong>💡 نکته مهم:</strong> ترتیب ثبت سرویس‌ها در DI Container اهمیت ندارد، زیرا .NET Core DI Container به صورت خودکار وابستگی‌ها را حل می‌کند. مهم این است که تمام سرویس‌های مورد نیاز ثبت شده باشند.
</div>
</div>
<!-- Section 6: Verification -->
<div class="section" id="verification">
<h2>✅ گام ۶: تأیید رفع خطا</h2>
<h3>خلاصه تغییرات:</h3>
<table>
<tr>
<th>فایل</th>
<th>تغییر</th>
<th>دلیل</th>
</tr>
<tr>
<td><code>XDefaultAIOCRService.cs</code></td>
<td><span class="badge-remove">REMOVE</span> حذف پارامتر <code>IXFileContentExtractor</code></td>
<td>پارامتر غیرضروری بود و باعث Circular Dependency می‌شد</td>
</tr>
<tr>
<td><code>Startup.cs</code></td>
<td><span class="badge-modify">NO CHANGE</span> بدون تغییر</td>
<td>ثبت سرویس‌ها به درستی انجام شده است</td>
</tr>
</table>
<h3>مزایای این اصلاح:</h3>
<div class="arch-grid">
<div class="arch-card">
<h4>🚫 حذف Circular Dependency</h4>
<ul>
<li>حلقه وابستگی کاملاً شکسته شد</li>
<li>DI Container می‌تواند تمام سرویس‌ها را بسازد</li>
</ul>
</div>
<div class="arch-card">
<h4>⚡ افزایش Performance</h4>
<ul>
<li>سرویس OCR دیگر بار اضافی ندارد</li>
<li>تزریق وابستگی‌های کمتر = ساخت سریع‌تر</li>
</ul>
</div>
<div class="arch-card">
<h4>🎯 اصل تک‌وظیفه‌ای (SRP)</h4>
<ul>
<li>سرویس OCR فقط مسئول ارتباط با LLM است</li>
<li>وابستگی‌های غیرضروری حذف شدند</li>
</ul>
</div>
<div class="arch-card">
<h4>🧪 قابلیت تست‌پذیری</h4>
<ul>
<li>Unit Test ساده‌تر شد</li>
<li>Mock کردن وابستگی‌ها آسان‌تر است</li>
</ul>
</div>
</div>
<div class="alert alert-success">
<strong>✅ نتیجه نهایی:</strong>
<p>با حذف پارامتر غیرضروری <code>IXFileContentExtractor</code> از constructor کلاس <code>XDefaultAIOCRService</code>، خطای Circular Dependency کاملاً رفع می‌شود و پروژه بدون مشکل اجرا خواهد شد.</p>
<p style="margin-top: 10px;">پس از اعمال این تغییر، پروژه را rebuild کنید و خطای <code>System.AggregateException</code> دیگر ظاهر نخواهد شد.</p>
</div>
</div>
</div>
<div class="footer">
<p><strong>👨‍💻 توسعه‌دهنده:</strong> هادی خزاعی اصل</p>
<p><strong>🏢 شرکت:</strong> فن آوران ساحر علم</p>
<p><strong>📅 تاریخ:</strong> شنبه ۱۲ مهر ۱۴۰۵</p>
<p style="margin-top: 15px; opacity: 0.8; font-size: 0.9em;">
🔗 مستند فنی رفع خطای Circular Dependency در xAiApi - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>