داخل الجزء الأولفهمنا المفهوم الرفيع المستوى: استعادة المعلومات ذات الصلة، ثم استخدامها لتوليد ردود الفعل. الآن نغوص بعمق في العمارة التقنية - بالضبط كيف تعمل أنظمة RAG تحت غطاء المحرك، من استراتيجيات تقسيم إلى داخلية LLM مثل الرسوم البيانية ومخابئ KV.
مجموعة القواعد النموذجية للملاحة: هذا هو الجزء الثاني من سلسلة RAG:
إذا لم تقرأ الجزء الأول، أوصي بالبداية هناك لفهم:
هذه المادة تفترض أنك تفهم تلك الأساسيات وتركز على الهندسة الهيكلية التقنية، وتفاصيل التنفيذ، وضوابط الإدارة المحلية للأراضي.
دعونا نقسم بالضبط ما يحدث في نظام RAG، من اللحظة التي تضيف فيها مستنداً إلى الوقت الذي يحصل فيه المستخدم على جواب.
قبل أن يستطيع RAG استرجاع أي شيء، تحتاج إلى فهرسة قاعدة معرفتك. هذه عملية لمرة واحدة (بالرغم من أنه يمكنك إضافة وثائق جديدة في وقت لاحق).
flowchart TB
A[Source Documents] -->|1. Extract Text| B[Text Extraction]
B -->|2. Split into Chunks| C[Chunking Service]
C -->|3. Generate Embeddings| D[Embedding Model]
D -->|4. Store Vectors| E[Vector Database]
B -.Metadata.-> E
subgraph "Example: Blog Post"
F["Understanding Docker: A containerization platform..."]
end
subgraph "Chunks"
G["Chunk 1: Title + Intro"]
H["Chunk 2: Benefits Section"]
end
subgraph "Embeddings"
I["0.234, 0.891, 0.567, ..."]
J["0.445, 0.123, 0.789, ..."]
end
F --> G
F --> H
G --> I
H --> J
style D stroke:#f9f,stroke-width:2px
style E stroke:#bbf,stroke-width:2px
يمكن أن يكون هذا النص كما يلي:
من مدونتي:
// From MarkdownRenderingService
public string ExtractPlainText(string markdown)
{
// Remove code blocks
var withoutCode = Regex.Replace(markdown, @"```[\s\S]*?```", "");
// Convert markdown to plain text
var document = Markdown.Parse(withoutCode);
var plainText = document.ToPlainText();
return plainText.Trim();
}
هذا هو المكان الذي تفشل فيه معظم عمليات تنفيذ RAG. لا يمكنك فقط تقسيم على حدود الفقرة - تحتاج إلى قطع مترابطة بشكل دلالي.
لِمَ يَهْمُّ تَقْسِيمُ ٱلْأَمْرِ :
جاري:
Chunk 1: "Docker is a containerization platform. It allows you"
Chunk 2: "to package applications with their dependencies. This"
Chunk 3: "ensures consistency across environments."
ما يلي:
Chunk 1: "Docker is a containerization platform. It allows you to package applications with their dependencies. This ensures consistency across environments."
Chunk 2: "Benefits of Docker:
- Isolation: Each container runs in its own environment
- Portability: Containers run anywhere Docker is installed
- Efficiency: Lightweight compared to virtual machines"
من تطبيق بحثي الدهني:
public class TextChunker
{
private const int TargetChunkSize = 500; // ~500 words
private const int ChunkOverlap = 50; // 50 words overlap
public List<Chunk> ChunkDocument(string text, string sourceId)
{
var chunks = new List<Chunk>();
// Split on section boundaries first (## headers in markdown)
var sections = SplitOnHeaders(text);
foreach (var section in sections)
{
// If section is small enough, keep it whole
if (section.WordCount < TargetChunkSize)
{
chunks.Add(new Chunk
{
Text = section.Text,
SourceId = sourceId,
SectionHeader = section.Header
});
}
else
{
// Split large sections on sentence boundaries
var subChunks = SplitOnSentences(section.Text, TargetChunkSize, ChunkOverlap);
chunks.AddRange(subChunks.Select(c => new Chunk
{
Text = c,
SourceId = sourceId,
SectionHeader = section.Header
}));
}
}
return chunks;
}
}
استراتيجيات تجميع الموازنات:
الـ هو سحر هو الذي يجعل البحث الدلالي ممكن الضم هو a متجه من الـ نص.
المفهوم الرئيسي: 2 - دال - دال - دال - دال - دال - دال - دال -
"Docker container" → [0.234, -0.891, 0.567, ..., 0.123]
"containerization platform" → [0.221, -0.903, 0.534, ..., 0.119]
"apple fruit" → [0.891, 0.234, -0.567, ..., -0.789]
المتجهان الأولان سيكونان "مغلقين" في فضاء المتجه (مشابهة التمام العالي)، في حين أن المتجه الثالث بعيد جداً.
كيف يتم توليد المقومات: نماذج الترسيخ الحديثة هي شبكات عصبية مدربة على مجموعات بيانات نصية ضخمة لتعلم العلاقات الدلالية. النماذج الشعبية:
مثال من خدمة التضمين OONNX:
public async Task<float[]> GenerateEmbeddingAsync(string text)
{
// Tokenize the input text
var tokens = Tokenize(text);
// Create input tensors for ONNX model
var inputIds = CreateInputTensor(tokens);
var attentionMask = CreateAttentionMaskTensor(tokens.Length);
var tokenTypeIds = CreateTokenTypeIdsTensor(tokens.Length);
// Run ONNX inference
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input_ids", inputIds),
NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask),
NamedOnnxValue.CreateFromTensor("token_type_ids", tokenTypeIds)
};
using var results = _session.Run(inputs);
// Extract the output (sentence embedding)
var output = results.First().AsTensor<float>();
var embedding = output.ToArray();
// L2 normalize the vector for cosine similarity
return NormalizeVector(embedding);
}
أسباب التطبيع: بعد التطبيع L2، تشابه تمام يصبح منتج نقطة بسيطة، مما يجعل البحث أسرع بكثير.
وتُحسَّن قواعد بيانات المتجهات إلى أقصى حد لتخزين النواقل العالية الأبعاد والبحث عنها. فبخلاف قواعد البيانات التقليدية التي تستخدم استفسارات SQL، تستخدم قواعد بيانات المتجهات بحث التشابه.
العمليات الرئيسية:
التنفيذ على سبيل المثال:
public async Task IndexDocumentAsync(
string id,
float[] embedding,
Dictionary<string, object> metadata)
{
var point = new PointStruct
{
Id = new PointId { Uuid = id },
Vectors = embedding,
Payload =
{
["title"] = metadata["title"],
["source"] = metadata["source"],
["chunk_index"] = metadata["chunk_index"],
["created_at"] = DateTime.UtcNow.ToString("O")
}
};
await _client.UpsertAsync(
collectionName: "blog_posts",
points: new[] { point }
);
}
قواعد بيانات النواقل الشعبية:
سنستكشف إنشاء قواعد البيانات هذه في المقالات القادمة
وعندما يطرح أحد المستعملين سؤالاً، يحتاج نظام RAG إلى العثور على أكثر المعلومات صلة بالموضوع من قاعدة المعارف.
flowchart LR
A["User Query:<br/>'How do I use Docker Compose?'"] --> B[Generate Query Embedding]
B --> C["Query Vector:<br/>[0.445, -0.123, ...]"]
C --> D[Vector Search]
D --> E[Vector Database]
E --> F[Top K Similar Chunks]
F --> G["Results:<br/>1. Docker Compose Basics 0.92<br/>2. Multi-Container Setup 0.87<br/>3. Service Configuration 0.83"]
style B stroke:#f9f,stroke-width:3px
style D stroke:#bbf,stroke-width:3px
سؤال المستخدم يحصل تحويلاً إلى متجه مستخدماً نموذج وهذا أمر حاسم - فالنماذج المختلفة تنتج متجهات غير متوافقة.
public async Task<List<SearchResult>> SearchAsync(string query, int limit = 10)
{
// Same embedding model used for indexing
var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(query);
// Search in vector store
var results = await _vectorStoreService.SearchAsync(
queryEmbedding,
limit
);
return results;
}
قاعدة بيانات المتجه computes تشابه بين متجه الاشارة وجميع المتجهات المختزنة.
التشابه (الأكثر شعبية بالنسبة إلى المتجهات المُتَطَوَّدة):
similarity = (A · B) / (||A|| × ||B||)
المدى: -1 إلى 1 (الارتفاع = أكثر تشابهاً)
عن طريق Euclisity (للمتجهات غير المتماثلة):
distance = sqrt(Σ(Ai - Bi)²)
المدى: 0 إلى كيلوغرام (الأدنى = أكثر تشابهاً)
**** (عندما تكون المتجهات سابقة للتصنيف):
similarity = A · B
المدى: -1 إلى 1 (الارتفاع = أكثر تشابهاً)
مِنْ خدمةِ Qdtrantي:
var searchResults = await _client.SearchAsync(
collectionName: "blog_posts",
vector: queryEmbedding,
limit: (ulong)limit,
scoreThreshold: 0.7f, // Only return results with >70% similarity
payloadSelector: true // Include all metadata
);
return searchResults.Select(hit => new SearchResult
{
Text = hit.Payload["text"].StringValue,
Title = hit.Payload["title"].StringValue,
Score = hit.Score,
Source = hit.Payload["source"].StringValue
}).ToList();
الاسترجاع الأولي سريع ولكن تقريبي. إعادة الترتيب تستخدم نموذج أكثر تطوراً لاستعادة نتائج K العليا.
flowchart LR
A[Vector Search:<br/>Top 50 Results] --> B[Reranking Model]
B --> C[Reranked:<br/>Top 10 Results]
style B stroke:#f9f,stroke-width:3px
لماذا تساعد إعادة الترتيب :
يُعاد ترتيب التنفيذ:
public async Task<List<SearchResult>> SearchWithRerankAsync(
string query,
int initialLimit = 50,
int finalLimit = 10)
{
// Stage 1: Fast vector search
var candidates = await SearchAsync(query, initialLimit);
// Stage 2: Precise reranking
var rerankedResults = await _rerankingService.RerankAsync(
query,
candidates
);
return rerankedResults.Take(finalLimit).ToList();
}
الآن بما أن لدينا معلومات ذات صلة، نقوم بتغذيتها إلى LLM جنبا إلى جنب مع سؤال المستخدم.
flowchart TB
A[User Query] --> B[Retrieved Context 1]
A --> C[Retrieved Context 2]
A --> D[Retrieved Context 3]
B --> E[Construct Prompt]
C --> E
D --> E
A --> E
E --> F["System: You are a helpful assistant...\n\nContext:\n1. Docker Compose allows...\n2. Services are defined...\n3. Volumes persist data...\n\nQuestion: How do I use Docker Compose?\n\nAnswer:"]
F --> G[LLM]
G --> H[Generated Answer with Citations]
style E stroke:#f9f,stroke-width:2px
style G stroke:#bbf,stroke-width:2px
هنا حيث يصبح RAG فنًا. تحتاج إلى تركيب التسارع لذلك LLM:
نموذج سريع من نظام محاميي GPT:
public string BuildRAGPrompt(string query, List<SearchResult> context)
{
var sb = new StringBuilder();
sb.AppendLine("You are a technical writing assistant. Your task is to answer the user's question using ONLY the provided context from past blog posts.");
sb.AppendLine();
sb.AppendLine("CONTEXT:");
sb.AppendLine("========");
for (int i = 0; i < context.Count; i++)
{
sb.AppendLine($"[{i + 1}] {context[i].Title}");
sb.AppendLine($"Source: {context[i].Source}");
sb.AppendLine($"Content: {context[i].Text}");
sb.AppendLine($"Relevance: {context[i].Score:P0}");
sb.AppendLine();
}
sb.AppendLine("========");
sb.AppendLine();
sb.AppendLine("INSTRUCTIONS:");
sb.AppendLine("- Answer the question using the provided context");
sb.AppendLine("- Cite sources using [1], [2], etc.");
sb.AppendLine("- If the context doesn't contain enough information, say so");
sb.AppendLine("- Maintain the technical, practical tone of the blog");
sb.AppendLine();
sb.AppendLine($"QUESTION: {query}");
sb.AppendLine();
sb.AppendLine("ANSWER:");
return sb.ToString();
}
الـ مشيّد الـ إلى LLM لـ توليد هذا:
مثال باستخدام LLLL محلياً:
public async Task<string> GenerateResponseAsync(string prompt)
{
var result = await _llamaSharp.InferAsync(prompt, new InferenceParams
{
Temperature = 0.7f, // Creativity (0 = deterministic, 1 = creative)
TopP = 0.9f, // Nucleus sampling
MaxTokens = 500, // Response length limit
StopSequences = new[] { "\n\n", "User:", "Question:" }
});
return result.Text.Trim();
}
(أ) البارامترات الرئيسية الموضحة:
وبعد أن تولد الإدارة المستدامة للأراضي استجابة، نحتاج في كثير من الأحيان إلى ما يلي:
مرحلة ما بعد التجهيز:
public RAGResponse PostProcess(string llmOutput, List<SearchResult> sources)
{
var response = new RAGResponse
{
Answer = llmOutput,
Sources = new List<Source>()
};
// Extract citations like [1], [2]
var citations = Regex.Matches(llmOutput, @"\[(\d+)\]");
foreach (Match match in citations)
{
int index = int.Parse(match.Groups[1].Value) - 1;
if (index >= 0 && index < sources.Count)
{
var source = sources[index];
response.Sources.Add(new Source
{
Title = source.Title,
Url = GenerateUrl(source.Source),
RelevanceScore = source.Score
});
}
}
// Convert markdown citations to hyperlinks
response.FormattedAnswer = Regex.Replace(
llmOutput,
@"\[(\d+)\]",
m => {
int index = int.Parse(m.Groups[1].Value) - 1;
if (index >= 0 && index < sources.Count)
{
var url = GenerateUrl(sources[index].Source);
return $"[[{m.Groups[1].Value}]]({url})";
}
return m.Value;
}
);
return response;
}
قبل أن ننتقل إلى التطبيقات العملية، من الضروري فهم كيفية عمل LLMs داخليا. هذه المعرفة تساعدك على تحسين أنظمة RAG وتجنب المزالق المشتركة.
التوكنات هي الوحدات الأساسية التي يعالجها LLLMs. النص لا يغذي مباشرة إلى النماذج -- إنه أولاً يقسم إلى رموز.
& & & & &:
Input: "Understanding Docker containers"
Tokens: ["Under", "standing", " Docker", " containers"]
تستخدم النماذج المختلفة استراتيجيات مختلفة للرمز:
السبب في أن التسويف مهم بالنسبة للفريق:
public class TokenCounter
{
// Rough approximation: 1 token ≈ 0.75 words (English)
public int EstimateTokens(string text)
{
var wordCount = text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
return (int)(wordCount / 0.75);
}
public int EstimateTokensAccurate(string text, ITokenizer tokenizer)
{
// Use actual tokenizer for precision
return tokenizer.Encode(text).Count;
}
}
& حدود النافذة:
في أنظمة RAG، يجب أن تلائم:
Total tokens = System prompt + Retrieved context + User query + Response buffer
إذا كان فريقك يسترجع 10 وثائق من 500 رمز لكل منها، هذا هو 5000 رمز فقط للسياق - قبل الاستعلام والإجابة!
(ج) الإدارة الصورية العملية RAG:
public class ContextWindowManager
{
private readonly int _maxContextTokens;
private readonly int _systemPromptTokens;
private readonly int _responseBufferTokens;
public ContextWindowManager(
int totalContextWindow = 4096,
int systemPromptTokens = 300,
int responseBufferTokens = 500)
{
_maxContextTokens = totalContextWindow;
_systemPromptTokens = systemPromptTokens;
_responseBufferTokens = responseBufferTokens;
}
public List<SearchResult> FitContextInWindow(
List<SearchResult> retrievedDocs,
string query)
{
var queryTokens = EstimateTokens(query);
// Available tokens for retrieved context
var availableForContext = _maxContextTokens
- _systemPromptTokens
- queryTokens
- _responseBufferTokens;
var selectedDocs = new List<SearchResult>();
var currentTokens = 0;
foreach (var doc in retrievedDocs.OrderByDescending(d => d.Score))
{
var docTokens = EstimateTokens(doc.Text);
if (currentTokens + docTokens <= availableForContext)
{
selectedDocs.Add(doc);
currentTokens += docTokens;
}
else
{
break; // Context window full
}
}
return selectedDocs;
}
private int EstimateTokens(string text)
{
// Rule of thumb: 1 token ≈ 4 characters
return text.Length / 4;
}
}
عندما تقوم LLLLM بتوليد النص، فإنه لا يعيد تجهيز كل شيء من الصفر لكل رمز. إنه يستخدم a مفتاح المخبأ (KVV) مخبئ أن نتذكر ما تم حسابه بالفعل.
المحولات تستخدم آلية "إهتمام" حيث كل رمز "يتوجه إلى" (النظرات على) جميع الرموز السابقة لفهم السياق.
flowchart TB
subgraph "Generation Step 1: 'Docker'"
A1[Input: 'Docker'] --> B1[Compute K,V for 'Docker']
B1 --> C1[Store in KV Cache]
C1 --> D1[Generate: 'is']
end
subgraph "Generation Step 2: 'is'"
A2[Input: 'is'] --> B2[Compute K,V for 'is']
B2 --> C2[Store in KV Cache]
C2 --> E2[Retrieve KV for 'Docker']
E2 --> F2[Attend: 'is' to 'Docker']
F2 --> D2[Generate: 'a']
end
subgraph "Generation Step 3: 'a'"
A3[Input: 'a'] --> B3[Compute K,V for 'a']
B3 --> C3[Store in KV Cache]
C3 --> E3[Retrieve KV for 'Docker', 'is']
E3 --> F3[Attend: 'a' to all previous]
F3 --> D3[Generate: 'container']
end
D1 --> A2
D2 --> A3
style C1 stroke:#f9f,stroke-width:3px
style C2 stroke:#f9f,stroke-width:3px
style C3 stroke:#f9f,stroke-width:3px
بدون KV:
مع KV
هذا يكوّل - - - - - - - - - - - - - - - - - - - - - - الفرق بين 10 رمزات/ثانية و100 رمز/ثانية.
المخبأ KV يتشكل من "شجرة" بسبب كيفية عمل الاهتمام في المحولات. كل طبقة في النموذج لديها مصفوفات K، V الخاصة بها.
graph TB
A[Input Tokens:<br/>'What is Docker?'] --> B[Layer 1 Attention]
B --> C[Layer 1 KV Cache]
B --> D[Layer 2 Attention]
D --> E[Layer 2 KV Cache]
D --> F[Layer 3 Attention]
F --> G[Layer 3 KV Cache]
F --> H[... up to Layer N]
H --> I[Output: 'Docker is']
C -.Key-Value pairs<br/>for all input tokens.-> C
E -.Key-Value pairs<br/>for all input tokens.-> E
G -.Key-Value pairs<br/>for all input tokens.-> G
style C stroke:#bbf,stroke-width:2px
style E stroke:#bbf,stroke-width:2px
style G stroke:#bbf,stroke-width:2px
(أ) كل مخزن:
لنموذج يحتوي على:
مخبئ KV لـ 1 هو:
2 (K and V) × 32 layers × 4096 dimensions × 8192 tokens × 2 bytes (FP16)
≈ 4.3 GB of VRAM!
وهذا هو السبب في أن نوافذ الإطار الطويل كثيفة الاستعمال للذاكرة.
يمكن لأنظمة RAG أن تُعزز مخبئ KV بطرائق ذكية:
**** (يدعمها بعض APIs مثل Anthropic كلود):
public class CachedRAGService
{
// System prompt and retrieved context can be cached!
public async Task<string> GenerateWithCachedContextAsync(
string systemPrompt, // Cached
List<SearchResult> context, // Cached
string userQuery) // Not cached, changes each time
{
var contextText = FormatContext(context);
// The KV cache for systemPrompt + contextText is reused across queries
var prompt = $@"
{systemPrompt}
CONTEXT:
{contextText}
QUERY: {userQuery}
ANSWER:";
return await _llm.GenerateAsync(prompt, useCaching: true);
}
}
لماذا هذا قوي:
المثال العملي:
Query 1: "How do I use Docker?" → 2 seconds (no cache)
Query 2: "What are Docker benefits?" → 0.2 seconds (cache hit!)
Query 3: "Docker vs VMs?" → 0.2 seconds (cache hit!)
وتستخدم جميع الاستفسارات الثلاثة السياق المسترجع نفسه، ولذلك يعاد استخدام مخبأ KV لذلك السياق.
رموز الفهم ومخبأ KV يُعلم قراراتك المعمارية:
قطع صغيرة = استرجاع أكثر دقة، ولكن أكثر زيادة في النفقات العامة:
// Option A: Small chunks (200 tokens each)
// Retrieve 20 chunks = 4,000 tokens
// Pro: Very precise, only relevant info
// Con: More KV cache entries, slower attention
// Option B: Larger chunks (500 tokens each)
// Retrieve 8 chunks = 4,000 tokens
// Pro: Better context coherence, fewer KV entries
// Con: More noise, less precise
public class AdaptiveChunker
{
public int DetermineChunkSize(int contextWindowSize)
{
if (contextWindowSize <= 4096)
return 200; // Small chunks for limited windows
if (contextWindowSize <= 16384)
return 500; // Medium chunks
return 1000; // Large chunks for big windows
}
}
لا تتجاوز نافذة السياق - تترك مساحة للجيل:
public class SafeContextManager
{
public int GetSafeContextLimit(int totalContextWindow)
{
// Use only 75% for input, reserve 25% for output
return (int)(totalContextWindow * 0.75);
}
// Example: 4K model
// Total: 4096 tokens
// Safe input: 3072 tokens
// Reserved for output: 1024 tokens
}
في الدردشة، تاريخ المحادثة ينمو مع كل دور:
Turn 1:
System + Context + Query1 = 3000 tokens
Response1 = 300 tokens
Total: 3300 tokens
Turn 2:
System + Context + Query1 + Response1 + Query2 = 3650 tokens
Response2 = 300 tokens
Total: 3950 tokens
Turn 3:
System + Context + Query1 + Response1 + Query2 + Response2 + Query3 = 4250 tokens
ERROR: Context window exceeded!
الإحلال: تزلج نافذة مع إعادة
public class ConversationalRAG
{
private readonly int _maxHistoryTokens = 1000;
public async Task<string> ChatAsync(
List<ConversationTurn> history,
string newQuery)
{
// Re-retrieve context based on current query
var context = await RetrieveContextAsync(newQuery);
// Keep only recent conversation history
var relevantHistory = TrimHistory(history, _maxHistoryTokens);
var prompt = BuildPrompt(context, relevantHistory, newQuery);
return await _llm.GenerateAsync(prompt);
}
private List<ConversationTurn> TrimHistory(
List<ConversationTurn> history,
int maxTokens)
{
var trimmed = new List<ConversationTurn>();
var currentTokens = 0;
// Keep most recent turns
foreach (var turn in history.Reverse())
{
var turnTokens = EstimateTokens(turn.Query) + EstimateTokens(turn.Response);
if (currentTokens + turnTokens <= maxTokens)
{
trimmed.Insert(0, turn);
currentTokens += turnTokens;
}
else
{
break;
}
}
return trimmed;
}
}
يمكن أن تفجر الشركة تكاليفها إن لم تكن حذرة:
public class CostAwareRAG
{
// OpenAI GPT-4 pricing (example):
// Input: $0.03 per 1K tokens
// Output: $0.06 per 1K tokens
public decimal EstimateQueryCost(
int systemPromptTokens,
int retrievedContextTokens,
int queryTokens,
int expectedResponseTokens)
{
var inputTokens = systemPromptTokens + retrievedContextTokens + queryTokens;
var outputTokens = expectedResponseTokens;
var inputCost = (inputTokens / 1000m) * 0.03m;
var outputCost = (outputTokens / 1000m) * 0.06m;
return inputCost + outputCost;
}
// Example:
// System: 300 tokens
// Context: 3000 tokens (10 retrieved docs)
// Query: 50 tokens
// Response: 500 tokens
//
// Cost = ((300 + 3000 + 50) / 1000 * 0.03) + (500 / 1000 * 0.06)
// = (3350 / 1000 * 0.03) + (500 / 1000 * 0.06)
// = $0.1005 + $0.03
// = $0.1305 per query
//
// At 1000 queries/day = $130/day = $3,900/month!
}
استراتيجيات خفض التكاليف:
وهنا كيف أن الرموز، KV مخبأ، وRAG تنسجم معا:
flowchart TB
A[User Query:<br/>'How does Docker work?'<br/>≈ 12 tokens] --> B[Generate Query Embedding]
B --> C[Vector Search]
C --> D[Retrieved Docs:<br/>5 docs × 500 tokens<br/>= 2,500 tokens]
D --> E[Construct Prompt]
A --> E
E --> F["Complete Prompt:<br/>System: 300 tokens<br/>Context: 2,500 tokens<br/>Query: 12 tokens<br/>Total: 2,812 tokens"]
F --> G[Tokenize Prompt]
G --> H["Token IDs:<br/>[245, 1034, 8829, ...]<br/>2,812 token IDs"]
H --> I[LLM Layer 1]
I --> J[Compute K,V]
J --> K[KV Cache Layer 1:<br/>2,812 K,V pairs]
I --> L[LLM Layer 2]
L --> M[Compute K,V]
M --> N[KV Cache Layer 2:<br/>2,812 K,V pairs]
L --> O[... Layers 3-32]
O --> P[Generate Token 1: 'Docker']
P --> Q[Add to KV Cache]
Q --> R[Generate Token 2: 'is']
R --> S[Add to KV Cache]
S --> T[... until completion]
T --> U["Response: 'Docker is a containerization platform...'<br/>≈ 400 tokens"]
style K stroke:#f9f,stroke-width:2px
style N stroke:#f9f,stroke-width:2px
style Q stroke:#bbf,stroke-width:2px
style S stroke:#bbf,stroke-width:2px
الرؤى الرئيسية:
تَفْكِيرات و KV يؤدي إلى تصميم أفضل RAG:
1- السيالتان المشتركتان السابقتان للخصائص والمختبأ:
// Cache KV for frequently used system prompts + static context
var cachedSystemContext = await _llm.PrecomputeKVCache(systemPrompt + staticContext);
// Reuse for each query (much faster)
foreach (var query in userQueries)
{
var response = await _llm.GenerateAsync(query, reuseKVCache: cachedSystemContext);
}
2 - الاستفادة المثلى من الحدود:
// Bad: Arbitrary 500-character chunks
var chunks = text.Chunk(500);
// Good: Chunk on sentence boundaries, measure in tokens
public List<string> ChunkByTokens(string text, int maxTokensPerChunk)
{
var sentences = SplitIntoSentences(text);
var chunks = new List<string>();
var currentChunk = new StringBuilder();
var currentTokens = 0;
foreach (var sentence in sentences)
{
var sentenceTokens = EstimateTokens(sentence);
if (currentTokens + sentenceTokens > maxTokensPerChunk && currentTokens > 0)
{
chunks.Add(currentChunk.ToString());
currentChunk.Clear();
currentTokens = 0;
}
currentChunk.Append(sentence).Append(" ");
currentTokens += sentenceTokens;
}
if (currentTokens > 0)
chunks.Add(currentChunk.ToString());
return chunks;
}
3 - استخدام لوحة المراقبة في الإنتاج:
public class RAGTelemetry
{
public void LogRAGQuery(
string query,
List<SearchResult> retrievedDocs,
string response)
{
var queryTokens = EstimateTokens(query);
var contextTokens = retrievedDocs.Sum(d => EstimateTokens(d.Text));
var responseTokens = EstimateTokens(response);
var totalTokens = queryTokens + contextTokens + responseTokens;
_logger.LogInformation(
"RAG Query: {Query} | Context: {ContextTokens} tokens from {DocCount} docs | " +
"Response: {ResponseTokens} tokens | Total: {TotalTokens} tokens",
query, contextTokens, retrievedDocs.Count, responseTokens, totalTokens
);
// Alert if approaching context limit
if (totalTokens > _maxTokens * 0.9)
{
_logger.LogWarning("Approaching token limit: {TotalTokens}/{MaxTokens}",
totalTokens, _maxTokens);
}
}
}
لقد غطينا التركيبة التقنية الكاملة لنظم RAG:
المرحلة الأولى: وضع الأرقام القياسية
المرحلة 2: المستردة
المرحلة 3: التوليد
الرؤى التقنية الرئيسية:
أنت الآن تفهم كيف يعمل RAG لكن النظرية فقط تحصل عليك حتى الآن. كيف يمكنك بناء هذه الأنظمة؟ ما هي التحديات التي ستواجهها؟ ما هي التقنيات المتقدمة التي يمكنك استخدامها؟
داخل **الجزء 3: المساعدة في الممارسة**ننتقل من الهيكل إلى التنفيذ:
تطبيقات العالم الحقيقي:
التحديات وحلول:
التقنيات المتقدمة:
بدأ:
مواصلة الجزء الثالث: الفريق الاستشاري المعني بالممارسة
أولاً - معلومات أساسية:
الأدوات والأطر:
اقرأ المزيد:
المجموعة الأولى:
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.