This commit is contained in:
2026-10-01 01:16:52 +03:30
parent 2eb1daf923
commit e9deaaa4b1
9 changed files with 45227 additions and 1 deletions
+977
View File
@@ -0,0 +1,977 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تحلیل معماری پروژه xAiApi - فن آوران ساحر علم</title>
<style>
:root {
--primary: #1e3a8a;
--secondary: #3b82f6;
--accent: #f59e0b;
--success: #10b981;
--danger: #ef4444;
--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;
position: relative;
}
.header::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="white" stroke-width="0.5" opacity="0.1"/></svg>');
opacity: 0.3;
}
.header h1 {
font-size: 2.2em;
margin-bottom: 10px;
position: relative;
}
.header .subtitle {
font-size: 1.1em;
opacity: 0.95;
position: relative;
}
.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.6em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.section h3 {
color: var(--secondary);
font-size: 1.2em;
margin: 20px 0 12px;
}
.step {
background: white;
padding: 20px;
margin: 15px 0;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border-right: 4px solid var(--accent);
}
.step-number {
display: inline-block;
background: var(--accent);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
text-align: center;
line-height: 32px;
font-weight: bold;
margin-left: 10px;
}
.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);
transition: transform 0.3s;
}
.arch-card:hover { transform: translateY(-5px); }
.arch-card h4 {
color: var(--primary);
margin-bottom: 12px;
font-size: 1.15em;
}
.arch-card ul {
list-style: none;
padding-right: 0;
}
.arch-card li {
padding: 6px 0;
padding-right: 20px;
position: relative;
font-size: 0.95em;
}
.arch-card li::before {
content: '▸';
position: absolute;
right: 0;
color: var(--accent);
font-weight: bold;
}
.tech-badge {
display: inline-block;
background: var(--secondary);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85em;
margin: 3px;
}
.tech-badge.primary { background: var(--primary); }
.tech-badge.success { background: var(--success); }
.tech-badge.accent { background: var(--accent); }
.tech-badge.danger { background: var(--danger); }
pre {
background: var(--bg-code);
color: #e2e8f0;
padding: 18px;
border-radius: 8px;
overflow-x: auto;
direction: ltr;
text-align: left;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 0.88em;
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;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
th {
background: var(--primary);
color: white;
padding: 12px;
text-align: right;
font-weight: bold;
}
td {
padding: 12px;
border-bottom: 1px solid var(--border);
}
tr:hover { background: var(--bg-light); }
.flow-diagram {
background: white;
padding: 25px;
border-radius: 10px;
margin: 20px 0;
text-align: center;
}
.flow-step {
display: inline-block;
background: var(--secondary);
color: white;
padding: 10px 20px;
border-radius: 8px;
margin: 5px;
font-size: 0.9em;
}
.flow-arrow {
display: inline-block;
color: var(--accent);
font-size: 1.5em;
margin: 0 8px;
vertical-align: middle;
}
.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-warning {
background: #fef3c7;
border-color: var(--accent);
color: #92400e;
}
.footer {
background: var(--primary);
color: white;
padding: 25px;
text-align: center;
margin-top: 40px;
}
.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;
transition: color 0.2s;
}
.toc a:hover { color: var(--primary); text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🧠 تحلیل جامع معماری پروژه xAiApi</h1>
<div class="subtitle">پلتفرم یکپارچه ارائه خدمات هوش مصنوعی مبتنی بر ASP.NET Core</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> 2026.09.30</div>
</div>
<div class="content">
<div class="toc">
<h3>📑 فهرست مطالب</h3>
<ol>
<li><a href="#overview">مرور کلی و چشم‌انداز پروژه</a></li>
<li><a href="#architecture">معماری کلان و ساختار ماژولار</a></li>
<li><a href="#layers">لایه‌بندی و مسئولیت‌ها</a></li>
<li><a href="#ai-providers">ارائه‌دهندگان هوش مصنوعی پشتیبانی شده</a></li>
<li><a href="#data-model">مدل داده و پایگاه داده</a></li>
<li><a href="#api-endpoints">نقاط پایانی API</a></li>
<li><a href="#features">ویژگی‌های کلیدی پیاده‌سازی شده</a></li>
<li><a href="#flow">جریان پردازش درخواست</a></li>
<li><a href="#di">مدیریت وابستگی‌ها (DI)</a></li>
<li><a href="#strengths">نقاط قوت و پیشنهادات بهبود</a></li>
<li><a href="#memory">خلاصه ذخیره شده در حافظه</a></li>
</ol>
</div>
<!-- Section 1: Overview -->
<div class="section" id="overview">
<h2>🎯 گام ۱: مرور کلی و چشم‌انداز پروژه</h2>
<p>
پروژه <span class="highlight">xAiApi</span> یک پلتفرم جامع و مقیاس‌پذیر برای ارائه خدمات هوش مصنوعی است که بر بستر
<code>ASP.NET Core</code> توسعه یافته است. این پروژه با رویکرد <strong>Modular Architecture</strong> طراحی شده
و قابلیت اتصال به چندین ارائه‌دهنده LLM را به صورت یکپارچه فراهم می‌کند.
</p>
<div class="alert alert-info">
<strong>💡 هدف اصلی:</strong> ایجاد یک لایه انتزاعی (Abstraction Layer) بر روی ارائه‌دهندگان مختلف AI
تا توسعه‌دهندگان بتوانند بدون وابستگی به یک ارائه‌دهنده خاص، از قابلیت‌های هوش مصنوعی در محصولات خود استفاده کنند.
</div>
<h3>سه ماژول اصلی پروژه:</h3>
<div class="arch-grid">
<div class="arch-card">
<h4>📡 xAiApi (لایه ارائه)</h4>
<ul>
<li>Controllers و API Endpoints</li>
<li>Database Context و Migrations</li>
<li>Providers و Service Implementations</li>
<li>Configuration و DI Extensions</li>
</ul>
</div>
<div class="arch-card">
<h4>🧩 xAiModels (لایه مدل)</h4>
<ul>
<li>Entities و DTOs</li>
<li>Repository Pattern</li>
<li>Enrichers و Data Providers</li>
<li>GraphQL Support</li>
</ul>
</div>
<div class="arch-card">
<h4>⚙️ xAiService (لایه سرویس)</h4>
<ul>
<li>Vector Helper (Embedding)</li>
<li>Cosine Similarity</li>
<li>User Validation Extensions</li>
<li>Configuration Node Names</li>
</ul>
</div>
</div>
</div>
<!-- Section 2: Architecture -->
<div class="section" id="architecture">
<h2>🏗️ گام ۲: معماری کلان و ساختار ماژولار</h2>
<p>
معماری پروژه بر پایه الگوی <strong>Clean Architecture</strong> همراه با <strong>Repository Pattern</strong>
و <strong>Dependency Injection</strong> گسترده طراحی شده است.
</p>
<h3>سلسله مراتب لایه‌ها:</h3>
<div class="flow-diagram">
<span class="flow-step">🌐 Client</span>
<span class="flow-arrow">→</span>
<span class="flow-step">📡 Controllers</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🧠 AI Services</span>
<span class="flow-arrow">→</span>
<span class="flow-step">💾 Data Provider</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🗄️ Repository</span>
</div>
<h3>ساختار پوشه‌ای پروژه xAiApi:</h3>
<pre>xAiApi/
├── 📂 Controllers/
│ ├── DefaultAiController.cs
│ ├── DefaultEmbeddingController.cs
│ ├── DefaultThinkingAiController.cs
│ ├── StartupController.cs
│ ├── XAiServiceControllerBase.cs
│ └── XAiEmbeddingServiceControllerBase.cs
├── 📂 Providers/
│ ├── XAIServiceBase.cs (هسته اصلی)
│ ├── XAiEmbeddingServiceBase.cs
│ ├── XDefaultAiService.cs
│ ├── XDefaultEmbeddingService.cs
│ └── XDefaultThinkingAiService.cs
├── 📂 Database/
│ ├── XAiApiDbContext.cs
│ └── XAiApiDatabaseDescriptor.cs
├── 📂 Interfaces/
│ ├── IXAiServiceBase.cs
│ ├── IXAiEmbeddingServiceBase.cs
│ └── IXDefault*.cs
├── 📂 Configurations/
│ └── XAiApiConfiguration.cs
├── 📂 Extensions/
│ ├── XModelsExtensions.cs
│ └── XProgramExtensions.cs
├── 📂 DI/
│ └── XDIHelperExtension.cs
├── 📂 Constants/
│ ├── XAiApiConstants.cs
│ └── ConfigurationNodeNames.cs
└── 📂 Migrations/
└── InitialMigrationAiApi.cs</pre>
</div>
<!-- Section 3: Layers -->
<div class="section" id="layers">
<h2>🧱 گام ۳: لایه‌بندی و مسئولیت‌ها</h2>
<h3>۳.۱ لایه Controllers (لایه ارائه)</h3>
<table>
<tr>
<th>Controller</th>
<th>مسئولیت</th>
<th>ویژگی کلیدی</th>
</tr>
<tr>
<td><code>DefaultAiController</code></td>
<td>پاسخ به سوالات عمومی</td>
<td>استفاده از مدل Gemma</td>
</tr>
<tr>
<td><code>DefaultEmbeddingController</code></td>
<td>تبدیل متن به بردار</td>
<td>پشتیبانی از Batch</td>
</tr>
<tr>
<td><code>DefaultThinkingAiController</code></td>
<td>استدلال عمیق (Reasoning)</td>
<td>مدل Qwen با Reasoning</td>
</tr>
<tr>
<td><code>StartupController</code></td>
<td>پیام خوش‌آمدگویی</td>
<td>AllowAnonymous</td>
</tr>
</table>
<div class="alert alert-success">
<strong>✅ نکته مهم:</strong> استفاده از <code>XBaseIdentityApiV1Controller</code> به عنوان کلاس پایه،
مدیریت هویت و مجوزها را به صورت یکپارچه فراهم می‌کند.
</div>
<h3>۳.۲ لایه Providers (هسته منطقی)</h3>
<p>کلاس <code>XAIServiceBase</code> به عنوان <strong>قلب تپنده</strong> پروژه عمل می‌کند و مسئولیت‌های زیر را بر عهده دارد:</p>
<ul style="padding-right: 25px; margin: 15px 0;">
<li>مدیریت ارتباط با LLM از طریق <code>IChatClient</code></li>
<li>پیاده‌سازی Memory Management برای حفظ Context مکالمات</li>
<li>پشتیبانی از Streaming Response (SSE)</li>
<li>مدیریت پروژه‌ها، مکالمات و پیام‌ها</li>
<li>تزریق Introduction Prompt به صورت خودکار</li>
</ul>
<h3>۳.۳ لایه Data Provider</h3>
<p>کلاس <code>XAiDataProvider</code> مسئولیت‌های زیر را مدیریت می‌کند:</p>
<ul style="padding-right: 25px; margin: 15px 0;">
<li>ایجاد خودکار <strong>Default Project</strong> و <strong>Default Conversation</strong> برای هر کاربر</li>
<li>مدیریت Multi-language Resources</li>
<li>ارسال SignalR Notifications</li>
<li>Enrichment اشیاء با اطلاعات مرتبط</li>
</ul>
</div>
<!-- Section 4: AI Providers -->
<div class="section" id="ai-providers">
<h2>🤖 گام ۴: ارائه‌دهندگان هوش مصنوعی پشتیبانی شده</h2>
<p>
پروژه از طریق <code>XAiModelProviderType</code> از چندین ارائه‌دهنده پشتیبانی می‌کند:
</p>
<div class="arch-grid">
<div class="arch-card">
<h4>🟢 OpenAI</h4>
<ul>
<li>استفاده از <code>OpenAIClient</code></li>
<li>پشتیبانی از ApiKey</li>
<li>Endpoint قابل تنظیم</li>
<li>Chat & Embedding</li>
</ul>
</div>
<div class="arch-card">
<h4>🔵 Ollama</h4>
<ul>
<li>اجرای Local Models</li>
<li>بدون نیاز به ApiKey</li>
<li>مناسب برای توسعه</li>
<li>مدل‌های Gemma, Qwen</li>
</ul>
</div>
<div class="arch-card">
<h4>🟡 DeepSeek</h4>
<ul>
<li>پشتیبانی تعریف شده</li>
<li>برای Reasoning Models</li>
<li>در حال توسعه</li>
</ul>
</div>
<div class="arch-card">
<h4>🟣 HuggingFace</h4>
<ul>
<li>دسترسی به هزاران مدل</li>
<li>پشتیبانی تعریف شده</li>
<li>در حال توسعه</li>
</ul>
</div>
</div>
<h3>مدل‌های پیش‌فرض پیکربندی شده:</h3>
<pre>// از XAiApiConstants.cs
XAiDefaultModelName = "Gemma" // مدل پیش‌فرض چت
XAiEmbeddingModelName = "Embed" // مدل Embedding
XAiDefaultThinkingModelName = "Qwen" // مدل Reasoning</pre>
<div class="alert alert-warning">
<strong>⚠️ نکته طراحی:</strong> استفاده از <code>Microsoft.Extensions.AI</code> به عنوان لایه انتزاعی استاندارد،
امکان تعویض ارائه‌دهنده بدون تغییر کد را فراهم می‌کند.
</div>
</div>
<!-- Section 5: Data Model -->
<div class="section" id="data-model">
<h2>🗄️ گام ۵: مدل داده و پایگاه داده</h2>
<p>پایگاه داده شامل ۶ جدول اصلی است:</p>
<table>
<tr>
<th>جدول</th>
<th>کلید</th>
<th>فیلدهای کلیدی</th>
<th>رابطه</th>
</tr>
<tr>
<td><code>AiProjects</code></td>
<td>Guid</td>
<td>OwnerId, Title, Description, Prompt</td>
<td>→ Conversations</td>
</tr>
<tr>
<td><code>AiConversations</code></td>
<td>Guid</td>
<td>OwnerId, Title, ProjectId</td>
<td>→ Messages, Project</td>
</tr>
<tr>
<td><code>AiMessages</code></td>
<td>Guid</td>
<td>Content, Role, MetaDatas, ConversationId</td>
<td>→ Conversation</td>
</tr>
<tr>
<td><code>Files</code></td>
<td>Guid</td>
<td>Name, Path, Thumb, References</td>
<td>مستقل</td>
</tr>
<tr>
<td><code>Strings</code></td>
<td>Int (Auto)</td>
<td>Language, ResourceTitle, TranslatedValue</td>
<td>Multi-language</td>
</tr>
<tr>
<td><code>Tags</code></td>
<td>Int (Auto)</td>
<td>Tag, References</td>
<td>مستقل</td>
</tr>
</table>
<h3>سلسله مراتب داده‌ها:</h3>
<div class="flow-diagram">
<span class="flow-step">👤 User (OwnerId)</span>
<span class="flow-arrow">→</span>
<span class="flow-step">📁 Project</span>
<span class="flow-arrow">→</span>
<span class="flow-step">💬 Conversation</span>
<span class="flow-arrow">→</span>
<span class="flow-step">📝 Message</span>
</div>
<div class="alert alert-info">
<strong>💡 ویژگی مهم:</strong> هر کاربر به صورت خودکار یک <strong>Default Project</strong> و یک
<strong>Default Conversation</strong> دارد که از طریق <code>XAiDataProvider</code> مدیریت می‌شود.
</div>
</div>
<!-- Section 6: API Endpoints -->
<div class="section" id="api-endpoints">
<h2>🔌 گام ۶: نقاط پایانی API</h2>
<h3>۶.۱ Endpoints عمومی (از XAiServiceControllerBase):</h3>
<table>
<tr>
<th>متد</th>
<th>مسیر</th>
<th>توضیح</th>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/AskText?question=</code></td>
<td>پرسش ساده و دریافت پاسخ</td>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/AskTextStream?question=</code></td>
<td>پرسش با پاسخ Streaming (SSE)</td>
</tr>
<tr>
<td><span class="tech-badge primary">POST</span></td>
<td><code>/Ask</code></td>
<td>پرسش با Context (Project/Conversation)</td>
</tr>
<tr>
<td><span class="tech-badge primary">POST</span></td>
<td><code>/AskStream</code></td>
<td>پرسش Context دار با Streaming</td>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/Projects</code></td>
<td>دریافت لیست پروژه‌ها</td>
</tr>
<tr>
<td><span class="tech-badge primary">POST</span></td>
<td><code>/Projects</code></td>
<td>ایجاد پروژه جدید</td>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/Projects/{id}/Conversations</code></td>
<td>مکالمات یک پروژه</td>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/Conversations/{id}/Messages</code></td>
<td>پیام‌های یک مکالمه</td>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>*/Query</code></td>
<td>جستجوی پیشرفته با XQuery</td>
</tr>
</table>
<h3>۶.۲ Endpoints Embedding:</h3>
<table>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/Embedding?content=</code></td>
<td>تبدیل یک متن به بردار</td>
</tr>
<tr>
<td><span class="tech-badge success">GET</span></td>
<td><code>/Embeddings?batch=</code></td>
<td>تبدیل دسته‌ای متون به بردار</td>
</tr>
</table>
</div>
<!-- Section 7: Features -->
<div class="section" id="features">
<h2>✨ گام ۷: ویژگی‌های کلیدی پیاده‌سازی شده</h2>
<div class="arch-grid">
<div class="arch-card">
<h4>🔄 Streaming Responses</h4>
<ul>
<li>پیاده‌سازی SSE (Server-Sent Events)</li>
<li>غیرفعال‌سازی Buffering</li>
<li>پاسخ بلادرنگ به کاربر</li>
</ul>
</div>
<div class="arch-card">
<h4>🧠 Memory Management</h4>
<ul>
<li>حفظ Context مکالمات</li>
<li>Introduction Prompt</li>
<li>Project-specific Prompts</li>
</ul>
</div>
<div class="arch-card">
<h4>🌐 Multi-language Support</h4>
<ul>
<li>Resource-based Localization</li>
<li>Owned Item Resources</li>
<li>Default Language Management</li>
</ul>
</div>
<div class="arch-card">
<h4>📡 Real-time Notifications</h4>
<ul>
<li>SignalR Hub</li>
<li>Project/Conversation/Message Events</li>
<li>Connection-aware Broadcasting</li>
</ul>
</div>
<div class="arch-card">
<h4>🔐 Authorization</h4>
<ul>
<li>OAuth2 Introspection</li>
<li>Policy-based Authorization</li>
<li>Role-based Access Control</li>
</ul>
</div>
<div class="arch-card">
<h4>📚 Swagger Documentation</h4>
<ul>
<li>XML Comments</li>
<li>API V1 Versioning</li>
<li>Auto-generated Docs</li>
</ul>
</div>
<div class="arch-card">
<h4>🎯 Reasoning Models</h4>
<ul>
<li>ReasoningEffort Configuration</li>
<li>ReasoningOutput Options</li>
<li>Thinking AI Service</li>
</ul>
</div>
<div class="arch-card">
<h4>📊 Vector Operations</h4>
<ul>
<li>Vector Normalization</li>
<li>Cosine Similarity</li>
<li>Batch Embedding</li>
</ul>
</div>
</div>
</div>
<!-- Section 8: Flow -->
<div class="section" id="flow">
<h2>🔄 گام ۸: جریان پردازش درخواست</h2>
<h3>۸.۱ جریان پرسش ساده (AskText):</h3>
<div class="flow-diagram">
<span class="flow-step">Client Request</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Controller</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Validation</span>
<span class="flow-arrow">→</span>
<span class="flow-step">XAIServiceBase</span>
<span class="flow-arrow">→</span>
<span class="flow-step">GetHistory()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">GetClient()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">LLM API</span>
</div>
<h3>۸.۲ جریان پرسش با Context (Ask):</h3>
<div class="flow-diagram">
<span class="flow-step">Request + ProjectId + ConversationId</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Load Project</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Load Conversation</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Load Messages</span>
<span class="flow-arrow">→</span>
<span class="flow-step">PrepareMemory()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Save User Message</span>
<span class="flow-arrow">→</span>
<span class="flow-step">AskLLM()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">Save AI Response</span>
</div>
<h3>۸.۳ جریان Embedding:</h3>
<div class="flow-diagram">
<span class="flow-step">Text Input</span>
<span class="flow-arrow">→</span>
<span class="flow-step">GetEmbeddingGenerator()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">GenerateAsync()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">VectorHelper.Normalize()</span>
<span class="flow-arrow">→</span>
<span class="flow-step">float[] Result</span>
</div>
</div>
<!-- Section 9: DI -->
<div class="section" id="di">
<h2>🔗 گام ۹: مدیریت وابستگی‌ها (DI)</h2>
<h3>سرویس‌های ثبت شده در <code>Startup.ConfigureServices</code>:</h3>
<pre>// سرویس‌های پایه
services.AddXCommons();
services.AddXAppConfiguration(Configuration);
services.AddXCors(appConfiguration.AllowedOrigins);
services.AddXSwagger(Configuration, xmlPath);
services.AddXHttpService(Configuration);
services.AddXApiV1Versioning();
services.AddXAuthorization();
// سرویس‌های هویت و ذخیره‌سازی
services.AddXIdentityService(lifeTime, Configuration);
services.AddXStorageService(Configuration);
services.AddXPushService(lifeTime, Configuration);
// سرویس‌های داده
services.AddXDatabase(lifeTime, descriptor, Configuration);
services.AddXStringService&lt;XAiApiDbContext&gt;(lifeTime, repositoryType);
services.AddXTagService&lt;XAiApiDbContext&gt;(lifeTime, repositoryType);
services.AddXFileService&lt;XAiApiDbContext&gt;(lifeTime, repositoryType);
services.AddXAiDataService&lt;XAiApiDbContext&gt;(lifeTime, repositoryType);
// سرویس‌های AI اختصاصی
services.AddScoped&lt;IXDefaultAiService, XDefaultAiService&gt;();
services.AddScoped&lt;IXDefaultEmbeddingService, XDefaultEmbeddingService&gt;();
services.AddScoped&lt;IXDefaultThinkingAiService, XDefaultThinkingAiService&gt;();</pre>
<div class="alert alert-success">
<strong>✅ الگوی طراحی:</strong> استفاده از <strong>Extension Methods</strong> برای ثبت سرویس‌ها
کد را تمیز، قابل نگهداری و ماژولار نگه می‌دارد.
</div>
</div>
<!-- Section 10: Strengths -->
<div class="section" id="strengths">
<h2>💎 گام ۱۰: نقاط قوت و پیشنهادات بهبود</h2>
<h3>✅ نقاط قوت:</h3>
<ul style="padding-right: 25px; margin: 15px 0;">
<li><strong>معماری ماژولار:</strong> جداسازی واضح لایه‌ها و مسئولیت‌ها</li>
<li><strong>Provider Agnostic:</strong> قابلیت تعویض ارائه‌دهنده AI بدون تغییر کد</li>
<li><strong>Repository Pattern:</strong> پشتیبانی از EF, MongoDB, InMemory</li>
<li><strong>Streaming Support:</strong> پیاده‌سازی کامل SSE برای UX بهتر</li>
<li><strong>Multi-language:</strong> سیستم Resource-based برای چندزبانگی</li>
<li><strong>Real-time:</strong> یکپارچگی SignalR برای Notifications</li>
<li><strong>GraphQL Support:</strong> امکان پرس‌وجوی انعطاف‌پذیر</li>
<li><strong>Enrichment Pattern:</strong> جداسازی منطق غنی‌سازی داده‌ها</li>
</ul>
<h3>🔧 پیشنهادات بهبود:</h3>
<ul style="padding-right: 25px; margin: 15px 0;">
<li>پیاده‌سازی کامل DeepSeek و HuggingFace providers</li>
<li>افزودن Rate Limiting برای کنترل مصرف API</li>
<li>پیاده‌سازی Caching برای پاسخ‌های پرتکرار</li>
<li>افزودن Telemetry و Monitoring (OpenTelemetry)</li>
<li>پیاده‌سازی Unit of Work Pattern به صورت کامل‌تر</li>
<li>افزودن Health Checks برای پایش سرویس‌ها</li>
<li>پیاده‌سازی Circuit Breaker برای resilience</li>
<li>افزودن Unit Tests و Integration Tests</li>
</ul>
</div>
<!-- Section 11: Memory Summary -->
<div class="section" id="memory">
<h2>🧠 گام ۱۱: خلاصه ذخیره شده در حافظه</h2>
<div class="alert alert-success">
<strong>✅ تأیید ذخیره‌سازی:</strong> تحلیل کامل پروژه در حافظه من ذخیره شد و برای دستورات بعدی شما آماده است.
</div>
<h3>📌 نکات کلیدی که به خاطر سپرده‌ام:</h3>
<table>
<tr>
<th>موضوع</th>
<th>جزئیات</th>
</tr>
<tr>
<td><strong>معماری</strong></td>
<td>Clean Architecture + Modular + Repository Pattern</td>
</tr>
<tr>
<td><strong>فناوری پایه</strong></td>
<td>ASP.NET Core, Entity Framework, SignalR, GraphQL</td>
</tr>
<tr>
<td><strong>ارائه‌دهندگان AI</strong></td>
<td>OpenAI, Ollama, DeepSeek, HuggingFace</td>
</tr>
<tr>
<td><strong>مدل‌های پیش‌فرض</strong></td>
<td>Gemma (Chat), Embed (Embedding), Qwen (Reasoning)</td>
</tr>
<tr>
<td><strong>ساختار داده</strong></td>
<td>Project → Conversation → Message</td>
</tr>
<tr>
<td><strong>ویژگی‌های کلیدی</strong></td>
<td>Streaming, Memory, Multi-lang, Real-time, Reasoning</td>
</tr>
<tr>
<td><strong>الگوهای طراحی</strong></td>
<td>DI, Repository, Provider, Enricher, Hub</td>
</tr>
<tr>
<td><strong>فایل‌های کلیدی</strong></td>
<td>XAIServiceBase.cs, XAiDataProvider.cs, Startup.cs</td>
</tr>
</table>
<h3>🎯 آماده برای دستورات بعدی:</h3>
<div class="arch-grid">
<div class="arch-card">
<h4>🔧 توسعه ویژگی جدید</h4>
<ul>
<li>افزودن Provider جدید</li>
<li>پیاده‌سازی Endpoint جدید</li>
<li>افزودن Entity جدید</li>
</ul>
</div>
<div class="arch-card">
<h4>🐛 رفع اشکال</h4>
<ul>
<li>تحلیل خطاهای runtime</li>
<li>بهینه‌سازی performance</li>
<li>بررسی memory leaks</li>
</ul>
</div>
<div class="arch-card">
<h4>📚 مستندسازی</h4>
<ul>
<li>API Documentation</li>
<li>Architecture Diagrams</li>
<li>User Guides</li>
</ul>
</div>
<div class="arch-card">
<h4>🚀 استقرار</h4>
<ul>
<li>Configuration Management</li>
<li>Deployment Strategies</li>
<li>Scaling Considerations</li>
</ul>
</div>
</div>
<div class="alert alert-info">
<strong>💬 پیام به استاد:</strong> تحلیل کامل پروژه با موفقیت انجام شد و تمامی جزئیات معماری،
ساختار کد، الگوهای طراحی و ویژگی‌های پیاده‌سازی شده در حافظه من ثبت گردید. اکنون آماده دریافت
دستورات بعدی شما برای توسعه، بهبود، یا هرگونه تغییر در پروژه هستم.
</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;">
🧠 تحلیل جامع معماری پروژه xAiApi - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff
+640
View File
@@ -0,0 +1,640 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>طراحی سیستم استخراج محتوا از فایل با مدل Qwen3.5 GGUF - فن آوران ساحر علم</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;
position: relative;
}
.header h1 { font-size: 2.2em; margin-bottom: 10px; position: relative; }
.header .subtitle { font-size: 1.1em; opacity: 0.95; position: relative; }
.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.6em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.section h3 { color: var(--secondary); font-size: 1.2em; margin: 20px 0 12px; }
.step {
background: white;
padding: 20px;
margin: 15px 0;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border-right: 4px solid var(--accent);
}
.step-number {
display: inline-block;
background: var(--accent);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
text-align: center;
line-height: 32px;
font-weight: bold;
margin-left: 10px;
}
.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);
transition: transform 0.3s;
}
.arch-card:hover { transform: translateY(-5px); }
.arch-card h4 { color: var(--primary); margin-bottom: 12px; font-size: 1.15em; }
.arch-card ul { list-style: none; padding-right: 0; }
.arch-card li { padding: 6px 0; padding-right: 20px; position: relative; font-size: 0.95em; }
.arch-card li::before { content: '▸'; position: absolute; right: 0; color: var(--accent); font-weight: bold; }
.tech-badge {
display: inline-block;
background: var(--secondary);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85em;
margin: 3px;
}
.tech-badge.primary { background: var(--primary); }
.tech-badge.success { background: var(--success); }
.tech-badge.accent { background: var(--accent); }
pre {
background: var(--bg-code);
color: #e2e8f0;
padding: 18px;
border-radius: 8px;
overflow-x: auto;
direction: ltr;
text-align: left;
font-family: 'Consolas', 'Courier New', 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;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
th { background: var(--primary); color: white; padding: 12px; text-align: right; font-weight: bold; }
td { padding: 12px; border-bottom: 1px solid var(--border); }
tr:hover { background: var(--bg-light); }
.flow-diagram {
background: white;
padding: 25px;
border-radius: 10px;
margin: 20px 0;
text-align: center;
}
.flow-step {
display: inline-block;
background: var(--secondary);
color: white;
padding: 10px 20px;
border-radius: 8px;
margin: 5px;
font-size: 0.9em;
}
.flow-arrow { display: inline-block; color: var(--accent); font-size: 1.5em; margin: 0 8px; vertical-align: middle; }
.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-warning { background: #fef3c7; border-color: var(--accent); color: #92400e; }
.footer { background: var(--primary); color: white; padding: 25px; text-align: center; margin-top: 40px; }
.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; transition: color 0.2s; }
.toc a:hover { color: var(--primary); text-decoration: underline; }
.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-new { background: var(--success); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.badge-modify { background: var(--warning); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🧠 استخراج هوشمند محتوا از فایل با مدل Qwen3.5 GGUF</h1>
<div class="subtitle">راهنمای گام به گام یکپارچه‌سازی مدل‌های محلی GGUF در معماری xAiApi</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="#overview">تحلیل رویکرد و استراتژی</a></li>
<li><a href="#step1">گام ۱: آماده‌سازی مدل GGUF در Ollama</a></li>
<li><a href="#step2">گام ۲: پیکربندی مدل در appsettings.json</a></li>
<li><a href="#step3">گام ۳: ایجاد سرویس استخراج محتوا (Extraction Service)</a></li>
<li><a href="#step4">گام ۴: ایجاد Controller اختصاصی</a></li>
<li><a href="#step5">گام ۵: ثبت وابستگی‌ها (DI)</a></li>
<li><a href="#flow">جریان کامل پردازش</a></li>
<li><a href="#tips">نکات کلیدی و مهندسی پرامپت</a></li>
</ol>
</div>
<!-- Section 1: Overview -->
<div class="section" id="overview">
<h2>🎯 گام ۱: تحلیل رویکرد و استراتژی</h2>
<p>
مدل <span class="highlight">Qwen3.5-9B-The-Defiant-Fable-Uncensored-Heretic-NEO-IMATRIX-MAX-MTP-GGUF</span>
یک مدل زبانی بزرگ (LLM) در فرمت <strong>GGUF</strong> است. برای اجرای این مدل و استفاده از آن جهت استخراج محتوا،
بهترین و سازگارترین راه با معماری فعلی پروژه شما، استفاده از <strong>Ollama</strong> به عنوان موتور اجرای محلی (Local Inference Engine) است.
</p>
<div class="alert alert-info">
<strong>💡 استراتژی دو مرحله‌ای استخراج محتوا:</strong><br>
۱. <strong>استخراج خام (Raw Extraction):</strong> استفاده از <code>IFileContentExtractor</code> (که قبلاً طراحی کردیم) برای خواندن متن خام از PDF/DOCX/TXT.<br>
۲. <strong>پردازش هوشمند (LLM Processing):</strong> ارسال متن خام به مدل Qwen3.5 با یک <strong>System Prompt</strong> دقیق برای خلاصه‌سازی، استخراج موجودیت‌ها (NER)، یا تبدیل به JSON ساختاریافته.
</div>
</div>
<!-- Section 2: Ollama Setup -->
<div class="section" id="step1">
<h2>⚙️ گام ۲: آماده‌سازی مدل GGUF در Ollama</h2>
<p>از آنجا که Ollama به صورت بومی از فرمت GGUF پشتیبانی می‌کند، باید مدل را دانلود و در Ollama ایمپورت کنیم.</p>
<div class="step">
<span class="step-number">۱</span>
<strong>دانلود فایل GGUF:</strong>
<p>فایل <code>.gguf</code> را از لینک Hugging Face ارائه شده دانلود کرده و در مسیری مانند <code>C:\Models\qwen3.5-defiant.gguf</code> ذخیره کنید.</p>
</div>
<div class="step">
<span class="step-number">۲</span>
<strong>ایجاد فایل Modelfile:</strong>
<p>یک فایل متنی بدون پسوند به نام <code>Modelfile</code> در کنار فایل GGUF ایجاد کنید و محتوای زیر را در آن قرار دهید:</p>
<pre>FROM C:/Models/qwen3.5-defiant.gguf
# تنظیم پارامترهای بهینه برای استخراج محتوا
PARAMETER temperature 0.2
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
# تنظیم پرامپت سیستمی پیش‌فرض برای استخراج ساختاریافته
SYSTEM """
تو یک دستیار هوشمند و دقیق برای استخراج و تحلیل محتوای اسناد هستی.
وظیفه تو خواندن متن ورودی، درک عمیق آن، و استخراج اطلاعات کلیدی به صورت ساختاریافته و دقیق است.
همیشه به زبان فارسی روان و حرفه‌ای پاسخ بده، مگر اینکه خلاف آن درخواست شود.
"""</pre>
</div>
<div class="step">
<span class="step-number">۳</span>
<strong>ایجاد مدل در Ollama:</strong>
<p>ترمینال یا CMD را باز کرده و دستور زیر را اجرا کنید تا مدل با یک نام مستعار (Alias) کوتاه ثبت شود:</p>
<pre>ollama create qwen3.5-defiant:9b -f Modelfile</pre>
<p>سپس برای اطمینان از صحت نصب، دستور <code>ollama list</code> را اجرا کنید.</p>
</div>
</div>
<!-- Section 3: Configuration -->
<div class="section" id="step2">
<h2>📝 گام ۳: پیکربندی مدل در appsettings.json</h2>
<p>اکنون باید این مدل جدید را به لیست مدل‌های مجاز در پیکربندی پروژه اضافه کنیم تا سرویس‌ها بتوانند از آن استفاده کنند.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/appsettings.json</span>
</div>
<pre>
"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}"
}
]
}
</pre>
<div class="alert alert-success">
<strong>✅ نکته:</strong> استفاده از نام مستعار <code>qwen3.5-defiant:9b</code> در فیلد <code>LLM</code> باعث می‌شود کد شما تمیز و خوانا بماند.
</div>
</div>
<!-- Section 4: Extraction Service -->
<div class="section" id="step3">
<h2>🔧 گام ۴: ایجاد سرویس استخراج محتوا (Extraction Service)</h2>
<p>ما یک سرویس اختصاصی ایجاد می‌کنیم که ترکیبی از <code>IFileContentExtractor</code> (برای خواندن فایل) و <code>IChatClient</code> (برای پردازش با Qwen) باشد.</p>
<div class="file-change">
<span class="badge-new">NEW</span>
<span class="path">xAiApi/Interfaces/IDocumentExtractionService.cs</span>
</div>
<pre>using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace xAiApi.Interfaces
{
public interface IDocumentExtractionService
{
/// &lt;summary&gt;
/// استخراج و تحلیل هوشمند محتوای یک فایل
/// &lt;/summary&gt;
Task&lt;string&gt; ExtractAndAnalyzeAsync(
IFormFile file,
string extractionPromptTemplate,
CancellationToken cancellationToken = default
);
}
}</pre>
<div class="file-change">
<span class="badge-new">NEW</span>
<span class="path">xAiApi/Providers/XDocumentExtractionService.cs</span>
</div>
<pre>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&lt;XDocumentExtractionService&gt; _logger;
public XDocumentExtractionService(
IFileContentExtractor fileExtractor,
XAiApiConfiguration configuration,
ILogger&lt;XDocumentExtractionService&gt; logger)
{
_fileExtractor = fileExtractor;
_configuration = configuration;
_logger = logger;
}
public async Task&lt;string&gt; 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();
}
}
}</pre>
</div>
<!-- Section 5: Controller -->
<div class="section" id="step4">
<h2>🎮 گام ۵: ایجاد Controller اختصاصی</h2>
<p>یک endpoint جدید برای دریافت فایل و بازگرداندن محتوای تحلیل‌شده ایجاد می‌کنیم.</p>
<div class="file-change">
<span class="badge-new">NEW</span>
<span class="path">xAiApi/Controllers/DocumentExtractionController.cs</span>
</div>
<pre>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&lt;DocumentExtractionController&gt; logger,
XAppConfiguration appConfiguration,
XValidationProvider validationProvider,
IXIdentityProvider identityProvider,
IDocumentExtractionService extractionService,
XAiApiConfiguration configuration)
: base(logger, appConfiguration, validationProvider, identityProvider)
{
_extractionService = extractionService;
_configuration = configuration;
}
/// &lt;summary&gt;
/// آپلود فایل و استخراج هوشمند محتوا با مدل Qwen3.5
/// &lt;/summary&gt;
[HttpPost("Extract")]
[Consumes("multipart/form-data")]
public async Task&lt;ActionResult&lt;object&gt;&gt; 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);
}
}
}
}</pre>
</div>
<!-- Section 6: DI -->
<div class="section" id="step5">
<h2>🔗 گام ۶: ثبت وابستگی‌ها (Dependency Injection)</h2>
<p>سرویس جدید را در متد <code>ConfigureServices</code> فایل <code>Startup.cs</code> ثبت کنید.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Startup.cs</span>
</div>
<pre>public void ConfigureServices(IServiceCollection services)
{
// ... (ثبت‌های قبلی)
// ثبت Extractor های فایل (اگر قبلاً ثبت نشده‌اند)
services.AddSingleton&lt;IFileContentExtractor, PlainTextContentExtractor&gt;();
services.AddSingleton&lt;IFileContentExtractor, PdfContentExtractor&gt;();
services.AddSingleton&lt;IFileContentExtractor, CompositeFileContentExtractor&gt;();
// ✅ ثبت سرویس جدید استخراج محتوا
services.AddScoped&lt;IDocumentExtractionService, XDocumentExtractionService&gt;();
// ... (بقیه کدها)
}</pre>
</div>
<!-- Section 7: Flow -->
<div class="section" id="flow">
<h2>🔄 گام ۷: جریان کامل پردازش</h2>
<div class="flow-diagram">
<span class="flow-step">📤 کلاینت: آپلود فایل</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🎮 DocumentExtractionController</span>
<span class="flow-arrow">→</span>
<span class="flow-step">📄 IFileContentExtractor (استخراج متن خام)</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🧠 Qwen3.5 GGUF (تحلیل و ساختارسازی)</span>
<span class="flow-arrow">→</span>
<span class="flow-step">✅ بازگرداندن JSON/متن تحلیل‌شده</span>
</div>
<h3>نمونه درخواست (cURL):</h3>
<pre>curl -X POST "http://localhost:5000/api/DocumentExtraction/Extract" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@/path/to/document.pdf" \
-F "promptName=DocumentExtraction"</pre>
</div>
<!-- Section 8: Tips -->
<div class="section" id="tips">
<h2>💡 گام ۸: نکات کلیدی و مهندسی پرامپت برای مدل‌های GGUF</h2>
<div class="arch-grid">
<div class="arch-card">
<h4>🎯 پرامپت‌نویسی دقیق</h4>
<p>مدل‌های GGUF محلی به دستورالعمل‌های شفاف بسیار خوب پاسخ می‌دهند. در <code>appsettings.json</code> حتماً قالب خروجی (مثلاً JSON) را به صراحت مشخص کنید.</p>
</div>
<div class="arch-card">
<h4>⚡ مدیریت Context Window</h4>
<p>در <code>Modelfile</code> مقدار <code>num_ctx</code> را بر اساس حجم فایل‌های شما تنظیم کنید (مثلاً 8192 یا 16384). اگر فایل بزرگ است، آن را به قطعات (Chunks) تقسیم کنید.</p>
</div>
<div class="arch-card">
<h4>🛡️ مدیریت خطا</h4>
<p>همیشه احتمال خطای OOM (کمبود حافظه RAM/VRAM) در مدل‌های 9B را در نظر بگیرید. لاگ‌های Ollama را برای پایش مصرف حافظه بررسی کنید.</p>
</div>
<div class="arch-card">
<h4>🖼️ پشتیبانی از تصویر (Vision)</h4>
<p>اگر این نسخه خاص از Qwen از ورودی تصویر پشتیبانی کند، می‌توانید در <code>XDocumentExtractionService</code> به جای متن خام، فایل تصویر را به <code>DataContent</code> تبدیل و ارسال کنید.</p>
</div>
</div>
<div class="alert alert-success">
<strong>✅ جمع‌بندی:</strong> با این طراحی، شما بدون تغییر در هسته اصلی <code>XAIServiceBase</code>، یک ماژول کاملاً ایزوله و قدرتمند برای استخراج محتوا با مدل‌های محلی GGUF ایجاد کرده‌اید که کاملاً با معماری ماژولار شرکت <strong>فن آوران ساحر علم</strong> همخوانی دارد.
</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;">
🧠 طراحی سیستم استخراج محتوا با Qwen3.5 GGUF - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>
+648
View File
@@ -0,0 +1,648 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>پیاده‌سازی استخراج محتوا با استفاده از xFileService در xAiApi - فن آوران ساحر علم</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;
position: relative;
}
.header h1 { font-size: 2.2em; margin-bottom: 10px; position: relative; }
.header .subtitle { font-size: 1.1em; opacity: 0.95; position: relative; }
.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.6em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.section h3 { color: var(--secondary); font-size: 1.2em; margin: 20px 0 12px; }
.step {
background: white;
padding: 20px;
margin: 15px 0;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border-right: 4px solid var(--accent);
}
.step-number {
display: inline-block;
background: var(--accent);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
text-align: center;
line-height: 32px;
font-weight: bold;
margin-left: 10px;
}
.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);
transition: transform 0.3s;
}
.arch-card:hover { transform: translateY(-5px); }
.arch-card h4 { color: var(--primary); margin-bottom: 12px; font-size: 1.15em; }
.arch-card ul { list-style: none; padding-right: 0; }
.arch-card li { padding: 6px 0; padding-right: 20px; position: relative; font-size: 0.95em; }
.arch-card li::before { content: '▸'; position: absolute; right: 0; color: var(--accent); font-weight: bold; }
.tech-badge {
display: inline-block;
background: var(--secondary);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85em;
margin: 3px;
}
.tech-badge.primary { background: var(--primary); }
.tech-badge.success { background: var(--success); }
.tech-badge.accent { background: var(--accent); }
pre {
background: var(--bg-code);
color: #e2e8f0;
padding: 18px;
border-radius: 8px;
overflow-x: auto;
direction: ltr;
text-align: left;
font-family: 'Consolas', 'Courier New', 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;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
th { background: var(--primary); color: white; padding: 12px; text-align: right; font-weight: bold; }
td { padding: 12px; border-bottom: 1px solid var(--border); }
tr:hover { background: var(--bg-light); }
.flow-diagram {
background: white;
padding: 25px;
border-radius: 10px;
margin: 20px 0;
text-align: center;
}
.flow-step {
display: inline-block;
background: var(--secondary);
color: white;
padding: 10px 20px;
border-radius: 8px;
margin: 5px;
font-size: 0.9em;
}
.flow-arrow { display: inline-block; color: var(--accent); font-size: 1.5em; margin: 0 8px; vertical-align: middle; }
.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-warning { background: #fef3c7; border-color: var(--accent); color: #92400e; }
.footer { background: var(--primary); color: white; padding: 25px; text-align: center; margin-top: 40px; }
.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; transition: color 0.2s; }
.toc a:hover { color: var(--primary); text-decoration: underline; }
.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-new { background: var(--success); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.badge-modify { background: var(--warning); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📎 پیاده‌سازی استخراج محتوا با ماژول xFileService</h1>
<div class="subtitle">یکپارچه‌سازی XFileProvider در XAiServiceBase برای پردازش فایل‌های ضمیمه</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="#strategy">استراتژی یکپارچه‌سازی</a></li>
<li><a href="#step1">گام ۱: به‌روزرسانی Controller برای آپلود فایل</a></li>
<li><a href="#step2">گام ۲: تزریق IXFileProvider به XAiServiceBase</a></li>
<li><a href="#step3">گام ۳: پیاده‌سازی منطق استخراج محتوا از فایل</a></li>
<li><a href="#step4">گام ۴: الحاق محتوا به ChatMessage و ذخیره Metadata</a></li>
<li><a href="#step5">گام ۵: به‌روزرسانی Extension مدل‌ها</a></li>
<li><a href="#flow">جریان کامل داده‌ها</a></li>
<li><a href="#summary">خلاصه تغییرات</a></li>
</ol>
</div>
<!-- Section 1: Strategy -->
<div class="section" id="strategy">
<h2>🎯 گام ۱: استراتژی یکپارچه‌سازی</h2>
<p>
به جای مدیریت مستقیم <code>IFormFile</code> در لایه سرویس هوش مصنوعی، از معماری تمیز (Clean Architecture) پیروی می‌کنیم:
</p>
<div class="arch-grid">
<div class="arch-card">
<h4>۱. لایه ارائه (Controller)</h4>
<ul>
<li>دریافت <code>IFormFileCollection</code></li>
<li>فراخوانی <code>IXFileProvider.Upload</code></li>
<li>دریافت لیست <code>XFileDto</code></li>
</ul>
</div>
<div class="arch-card">
<h4>۲. لایه سرویس (XAiServiceBase)</h4>
<ul>
<li>دریافت <code>IEnumerable&lt;XFileDto&gt;</code></li>
<li>فراخوانی <code>IXFileProvider.GetFileDescriptor</code></li>
<li>خواندن Stream و تبدیل به <code>AIContent</code></li>
</ul>
</div>
<div class="arch-card">
<h4>۳. لایه داده (Metadata)</h4>
<ul>
<li>ذخیره <code>FileId</code> در فیلد <code>MetaDatas</code> پیام</li>
<li>حفظ رابطه بدون نیاز به جدول Join جدید</li>
</ul>
</div>
</div>
</div>
<!-- Step 1 -->
<div class="section" id="step1">
<h2>🎮 گام ۲: به‌روزرسانی Controller برای آپلود فایل</h2>
<p>ابتدا باید فایل‌ها را از طریق <code>XFileProvider</code> آپلود کنیم تا <code>XFileDto</code> دریافت شود.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Controllers/XAiServiceControllerBase.cs</span>
</div>
<pre>// ۱. افزودن وابستگی به Constructor
private readonly IXFileProvider _fileProvider;
protected XAiServiceControllerBase(
// ... پارامترهای قبلی
IXFileProvider fileProvider // ✅ جدید
) : base(...)
{
// ...
_fileProvider = fileProvider;
}
// ۲. اصلاح متد Ask
[HttpPost("Ask")]
[Consumes("multipart/form-data")]
public async Task&lt;ActionResult&lt;string&gt;&gt; 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&lt;XFileDto&gt; 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);
}
}</pre>
<div class="alert alert-info">
<strong>💡 نکته:</strong> همین تغییر باید برای متد <code>AskStream</code> نیز اعمال شود.
</div>
</div>
<!-- Step 2 -->
<div class="section" id="step2">
<h2>⚙️ گام ۳: تزریق IXFileProvider به XAiServiceBase</h2>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Providers/XAIServiceBase.cs</span>
</div>
<pre>// ۱. افزودن فیلد و تزریق در Constructor
private readonly IXFileProvider _fileProvider;
protected XAIServiceBase(
IXAiDataProvider dataProvider,
ILogger&lt;XAIServiceBase&gt; 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&lt;XAiMessageDto&gt; AskAsync(
string prompt,
string ownerId,
Guid projectId,
Guid conversationId,
string connectionId = null,
IEnumerable&lt;XFileDto&gt; 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&lt;string, object&gt;
{
{ "AttachedFileIds", fileIds }
}.ToJSON();
}
promptMessage = await dataProvider.AddMessage(...);
// ✅ الحاق محتوا به ChatMessage
var promptChatMessage = promptMessage.ToChatMessages(fileContents);
var answer = await AskLLMAsync(history: history, prompt: promptChatMessage, cancellationToken: cancellationToken);
// ... (ذخیره پاسخ و بازگشت نتیجه)
}</pre>
</div>
<!-- Step 3 -->
<div class="section" id="step3">
<h2>🔍 گام ۴: پیاده‌سازی منطق استخراج محتوا از فایل</h2>
<p>این متد کمکی درون <code>XAiServiceBase</code> مسئول خواندن فایل از طریق <code>XFileProvider</code> و تبدیل آن به فرمت قابل فهم برای LLM است.</p>
<div class="file-change">
<span class="badge-new">NEW METHOD</span>
<span class="path">xAiApi/Providers/XAIServiceBase.cs</span>
</div>
<pre>/// &lt;summary&gt;
/// پردازش فایل‌های ضمیمه و تبدیل به AIContent
/// &lt;/summary&gt;
private async Task&lt;IList&lt;AIContent&gt;&gt; ProcessAttachedFilesAsync(
IEnumerable&lt;XFileDto&gt; files,
CancellationToken cancellationToken = default
)
{
var contents = new List&lt;AIContent&gt;();
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;
}</pre>
<div class="alert alert-success">
<strong>✅ مزیت:</strong> با استفاده از <code>GetFileDescriptor</code>، ماژول هوش مصنوعی نیازی به دانستن جزئیات سیستم فایل (File System) ندارد و کاملاً از <code>xFileService</code> انتزاع یافته است.
</div>
</div>
<!-- Step 4 -->
<div class="section" id="step4">
<h2>🔌 گام ۵: به‌روزرسانی Extension مدل‌ها</h2>
<p>متد <code>ToChatMessages</code> باید بتواند محتوای استخراج شده از فایل‌ها را در کنار متن اصلی پیام قرار دهد.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiModels/Extensions/XAiModelsExtensions.cs</span>
</div>
<pre>public static ChatMessage ToChatMessages(
this XAiMessageDto source,
IList&lt;AIContent&gt; additionalContents = null // ✅ پارامتر جدید
)
{
ChatMessage result = null;
if (!source.IsNullOrDefault())
{
var contents = new List&lt;AIContent&gt;();
// ۱. افزودن متن اصلی پیام (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;
}</pre>
</div>
<!-- Step 5 -->
<div class="section" id="step5">
<h2>🔗 گام ۶: ثبت وابستگی‌ها (Dependency Injection)</h2>
<p>اطمینان حاصل کنید که <code>IXFileProvider</code> در کانتینر DI ثبت شده است (که بر اساس فایل‌های ارائه شده، قبلاً در <code>xFileService.DI.XDIHelperExtension</code> انجام شده است). فقط باید اطمینان حاصل کنیم که در <code>xAiApi</code> قابل تزریق است.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Startup.cs</span>
</div>
<pre>public void ConfigureServices(IServiceCollection services)
{
// ... (سایر ثبت‌ها)
// ✅ اطمینان از ثبت سرویس فایل (اگر قبلاً در ماژول xFileService ثبت نشده، اینجا فراخوانی شود)
// services.AddXFileService&lt;XAiApiDbContext&gt;(xDataService.Constants.XRepositoryType.EF);
// ✅ به‌روزرسانی ثبت سرویس‌های AI برای تزریق IXFileProvider
// نکته: چون XAIServiceBase کلاس پایه است، باید در کلاس‌های مشتق شده (مثل XDefaultAiService) تزریق شود.
// مثال برای XDefaultAiService:
// services.AddScoped&lt;IXDefaultAiService&gt;(sp =&gt; new XDefaultAiService(
// sp.GetRequiredService&lt;IXAiDataProvider&gt;(),
// sp.GetRequiredService&lt;ILogger&lt;XDefaultAiService&gt;&gt;(),
// sp.GetRequiredService&lt;XAiApiConfiguration&gt;(),
// sp.GetRequiredService&lt;XValidationProvider&gt;(),
// sp.GetRequiredService&lt;IXFileProvider&gt;() // ✅ تزریق جدید
// ));
}</pre>
<div class="alert alert-warning">
<strong>⚠️ توجه:</strong> اگر <code>XAiServiceBase</code> را مستقیماً ثبت نمی‌کنید و از کلاس‌های مشتق شده استفاده می‌کنید، باید Constructor آن کلاس‌ها را نیز برای پذیرش <code>IXFileProvider</code> و پاس دادن آن به <code>base(...)</code> به‌روزرسانی کنید.
</div>
</div>
<!-- Flow -->
<div class="section" id="flow">
<h2>🔄 گام ۷: جریان کامل پردازش</h2>
<div class="flow-diagram">
<span class="flow-step">📤 کلاینت: ارسال Form (Prompt + Files)</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🎮 Controller: فراخوانی IXFileProvider.Upload</span>
<span class="flow-arrow">→</span>
<span class="flow-step">💾 xFileService: ذخیره فیزیکی و ثبت در DB</span>
<span class="flow-arrow">→</span>
<span class="flow-step">⚙️ XAiServiceBase: دریافت List&lt;XFileDto&gt;</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🔍 XAiServiceBase: فراخوانی GetFileDescriptor</span>
<span class="flow-arrow">→</span>
<span class="flow-step">📝 تبدیل Stream به TextContent/DataContent</span>
<span class="flow-arrow">→</span>
<span class="flow-step">🤖 ارسال ChatMessage (Text + Files) به LLM</span>
<span class="flow-arrow">→</span>
<span class="flow-step">💾 ذخیره Message با FileIds در MetaDatas</span>
</div>
</div>
<!-- Summary -->
<div class="section" id="summary">
<h2>📋 گام ۸: خلاصه تغییرات</h2>
<table>
<tr>
<th>ردیف</th>
<th>فایل / ماژول</th>
<th>نوع تغییر</th>
<th>توضیح</th>
</tr>
<tr>
<td>۱</td>
<td><code>xAiApi/Controllers/XAiServiceControllerBase.cs</code></td>
<td><span class="badge-modify">MODIFY</span></td>
<td>افزودن <code>IXFileProvider</code> و فراخوانی <code>Upload</code> قبل از سرویس AI</td>
</tr>
<tr>
<td>۲</td>
<td><code>xAiApi/Interfaces/IXAiServiceBase.cs</code></td>
<td><span class="badge-modify">MODIFY</span></td>
<td>تغییر پارامتر <code>files</code> از <code>IFormFileCollection</code> به <code>IEnumerable&lt;XFileDto&gt;</code></td>
</tr>
<tr>
<td>۳</td>
<td><code>xAiApi/Providers/XAIServiceBase.cs</code></td>
<td><span class="badge-modify">MODIFY</span></td>
<td>تزریق <code>IXFileProvider</code> و افزودن متد <code>ProcessAttachedFilesAsync</code></td>
</tr>
<tr>
<td>۴</td>
<td><code>xAiModels/Extensions/XAiModelsExtensions.cs</code></td>
<td><span class="badge-modify">MODIFY</span></td>
<td>پشتیبانی <code>ToChatMessages</code> از <code>additionalContents</code></td>
</tr>
<tr>
<td>۵</td>
<td><code>xAiApi/Providers/XDefaultAiService.cs</code> (و سایر مشتق‌ها)</td>
<td><span class="badge-modify">MODIFY</span></td>
<td>به‌روزرسانی Constructor برای پاس دادن <code>IXFileProvider</code> به کلاس پایه</td>
</tr>
</table>
<div class="alert alert-success">
<strong>✅ دستاوردهای این طراحی:</strong>
<ul style="padding-right: 25px; margin-top: 10px;">
<li>🛡️ <strong>جداسازی مسئولیت‌ها:</strong> ماژول AI دیگر درگیر آپلود یا مدیریت فایل فیزیکی نیست.</li>
<li>♻️ <strong>استفاده مجدد:</strong> از تمام قابلیت‌های <code>xFileService</code> (مانند Thumbnail، References، و Storage) بهره می‌بریم.</li>
<li>🔗 <strong>ردیابی‌پذیری:</strong> با ذخیره <code>FileId</code> در <code>MetaDatas</code>، همیشه می‌توانیم بفهمیم کدام فایل‌ها به کدام پیام متصل بوده‌اند.</li>
<li>🎨 <strong>پشتیبانی چندوجهی (Multi-modal):</strong> آماده‌سازی برای ارسال تصاویر به صورت <code>DataContent</code> به مدل‌های Vision.</li>
</ul>
</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;">
📎 پیاده‌سازی استخراج محتوا با ماژول xFileService - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1 -1
Submodule xAiApi updated: aac3eb92c1...f32972b55f