Back to "بناء "Lawyer GPT" لمدونتك - توجيه جيد - بديل LLL: RAG مع Qdrant وGeneric Online LLMs"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

AI C# Cloud LLM OpenAI Qdrant RAG

بناء "Lawyer GPT" لمدونتك - توجيه جيد - بديل LLL: RAG مع Qdrant وGeneric Online LLMs

Wednesday, 12 November 2025

أولاً

(ب) فيسلسلة "Lawer GPT"سأريكم كيف تبنيون مساعداً كاملاً للكتابة على أساس RAG باستخدام التسارع GPU، LLMs المحلي، وقواعد بيانات متجهة.

إنّه قويّ، خاصّ، ويُدار كليّاً على معدّاتِكَ.

ولكن دعونا نكون صادقين - ليس كل شخص لديه محطة عمل مع NVIDIA GPU، 96GB من رام، والصبر لإنشاء نماذج CUDA، وCUDNN، وWrling GGGUF.

ماذا لو كنت تريد فقط فوائد مدوّنة مساعد كتابة مساعد دون استثمار الأجهزة؟

تعرض هذه المادة البديل القائم على السحب: نفس نهج RAG، ونفس قاعدة بيانات متجهات Qdrant، ولكن باستخدام سحاب LLLL APIs بدلا من الاستدلال المحلي.

فكّر فيها على أنها "Lawyer GPT Lite" - إعداد أسهل، وحاجز أدنى للدخول، وربما نوعية أفضل من الناتج باستخدام نماذج الحدود.

ما زلت أتعلّم أيّ نهج يعمل أفضل من الناحية العملية، لذا خذ تقديرات تكاليفي ومطالبات الأداء مع قرصة من الملح.

ما يمكنني قوله هو أن هذا النهج السحابي قد أثبت بشكل واضح ومباشرة لوضع مقارنة مع مسار GPU.

  • ملاحظة: هذا جزء من تجاربي مع AI (مساعدة في الصياغة) + تحريري الخاص.
  • نفس الصوت، نفس العمليّة، مجرد أصابع أسرع.
  • لماذا بديل سحابة؟
  • النهج الأصلي
  • وتبني سلسلة "Lawyer GPT" الكاملة نظاماً يقوم بما يلي:
  • 100 في المائة محلياً (مخصوفاً)
  • تكاليف الرقم

سرعة GPU- مُسَجّل

8GGP+ VRAM)

  • التركيبة التركيبية (CUDA، CUDDN، إدارة النماذج)
  • محدود للنماذج التي يمكنك إدراجها في VRAM
  • النشر الذي يركز على النوافذ
  • بديل السحب
  • هذا النهج يعطيك:
  • لا حاجة إلى GPU (الركضات على أي آلة)
  • إعداد بسيط (لا CUDDA/ cuDNN)
  • الوصول إلى نماذج الحدود (GPT-4، كلود، وما إلى ذلك)

- المتقاطعة (ويندوز، ماك، لينكس)

نوعية نواتج أفضل (نماذج أكبر وأكثر قدرة) |--------------------|-------------------| تكاليف الرقم القياسي لأسعار الاستهلاك (وإن كان ذلك معقولاً للاستخدام الشخصي) البيانات المرسلة إلى الأطراف الثالثة يعتمد التأخر على الشبكة متى تستخدم أي منها؟

استخدام النهج المحلي نهج استخدام نهج السحب

الخصوصية هي أمر بالغ الأهمية في المقام الأول من الأمور المتعلقة بالمحيط

graph TB
    A[Markdown Files] -->|Ingest| B[Chunking Service]
    B -->|Text Chunks| C[Cloud Embedding API]
    C -->|Vectors| D[Qdrant Vector DB]

    E[User Writing] -->|Current Draft| F[Web/Desktop Client]
    F -->|Embed Context| C
    C -->|Query Vector| D
    D -->|Similar Content| G[Context Builder]

    G -->|Relevant Past Articles| H[Prompt Engineer]
    H -->|Prompt + Context| I[Cloud LLM API]
    I -->|Generated Suggestions| J[Response Handler]
    J -->|Suggestions + Citations| F

    F -->|Display| K[Editor with Suggestions]

    class C,I cloud
    class D,K local

    classDef cloud stroke:#f96,stroke-width:4px
    classDef local stroke:#333,stroke-width:2px

لديك معدات GPU GPU أنت على Mac/Linux/Labtup TM

  • استخدامات الحجم الكبير والاستخدامات المعتدلة (وظائف النفي/شهر)♪ هل تستمتع باللعب ♪ ♪ تريد نتائج سريعة ♪text-embedding-3-smallأولاً - لمحة عامة
  • **تحفظ النسخة السحابية نفس أساسيات RAG لكن مبادلات LLM المحلية لاستدلال نداءات API:**الاختلافات الرئيسية:
  • النموذج المُزْرِج:: OpenAI's
  • API بدلاً من النموذج المحلي BGGLLL

:كلود: 3,5 سونيت أو GPT-4 API بدلاً من المحلي Mistral/Llama

لا GPU مطلوب

كل شيء يقوم على أساس محلي كل شيء يحدث في سحابة

  • ** النشر**واحد تنفيذي ، لا نموذج ملفات إلى إدارة
  • **يجب أن ألاحظ أنني لم أقارن بين هذين النهجين حتى الآن - ما زلت في المرحلة الاستكشافية بنفسي.**ولكن النتائج الأولية واعدة بما فيه الكفاية لتقاسمها.

تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن تكن

  • الإطار الأساسيفي العالم ٩
  • - نفس السلاسل الأصليةC# 13
  • - الملامح اللغوية الحديثةQ Q Q

متوسط

  • **- التسميات (النصوص - المختلسة - 3 - الصغر) + LLL (GPT-4)**Athسيطي AP
  • - بديل LLLM (كلود 3-5 سونيت)

-يمكنك المزاوجة والتطابق

  • قاعدة بيانات الفئة
  • - نفس الأصل الأصلي، يمكن تشغيله محلياً عن طريق Docker أو استخدام Qdrant سحابالبديل
  • : Penecone, Weavianate سحابة (خيارات مدارة)& سو سو منفذ

المُقَسِل المُنْسِج

- المبسطة والكبيرة للاختبار

قرار على شبكة الإنترنت

docker run -p 6333:6333 -p 6334:6334 \
    -v $(pwd)/qdrant_storage:/qdrant/storage:z \
    qdrant/qdrant

- على شبكة الإنترنت، يعمل في أي مكان

  1. المائل- متقاطع لسطح مكتب (Windows, Mac, Linux)
  2. الإعداد: المسار السريع
  3. ١ -

الثبات

الخيار ألف: مقرر محلي (موصى به من أجل التنمية)

**الخيار باء: سُحْب Qdrant shall (أيسر)**ال_توقيع على

  1. سحابة من سحابة.إنشاء وحد
  2. تحصل على مفتاح و UR
  3. لا CUDA، لا cutdnn، لا تَطْلبُ تركيباتَ سائقِ!

**)٢( A.**احصل على مفاتيح مُ_ مُ_ مُ_ مُ_ مُ_ مُ_ مُ_ مُ_ مُ_ مُ_ مُ_ مُ

  1. POSAI(للمؤزززات + LML):
  2. إلى:

برنامج عمل برنامج الأمم المتحدة للبيئة.

appsettings.json:

{
  "BlogRAG": {
    "Embedding": {
      "Provider": "OpenAI",
      "Model": "text-embedding-3-small",
      "ApiKey": "sk-..."
    },
    "LLM": {
      "Provider": "Anthropic",
      "Model": "claude-3-5-sonnet-20241022",
      "ApiKey": "sk-ant-..."
    },
    "VectorStore": {
      "Type": "Qdrant",
      "Url": "http://localhost:6333",
      "ApiKey": "",
      "CollectionName": "blog_embeddings"
    },
    "Ingestion": {
      "MarkdownPath": "/path/to/your/blog/Markdown",
      "ChunkSize": 500,
      "ChunkOverlap": 50
    }
  }
}

تعيين حدود الاستعمال (أهم!)

(الخيار، بالنسبة لكل ما يلي):

إلى:

(أ) الحساب الأساسي.

using OpenAI;
using OpenAI.Embeddings;

namespace BlogRAG.Services
{
    public interface IEmbeddingService
    {
        Task<float[]> GenerateEmbeddingAsync(string text);
        Task<List<float[]>> GenerateBatchEmbeddingsAsync(List<string> texts);
    }

    public class OpenAIEmbeddingService : IEmbeddingService
    {
        private readonly OpenAIClient _client;
        private readonly string _model;
        private readonly ILogger<OpenAIEmbeddingService> _logger;

        public OpenAIEmbeddingService(
            string apiKey,
            string model,
            ILogger<OpenAIEmbeddingService> logger)
        {
            _client = new OpenAIClient(apiKey);
            _model = model;
            _logger = logger;
        }

        public async Task<float[]> GenerateEmbeddingAsync(string text)
        {
            var embeddings = await GenerateBatchEmbeddingsAsync(new List<string> { text });
            return embeddings.First();
        }

        public async Task<List<float[]>> GenerateBatchEmbeddingsAsync(List<string> texts)
        {
            _logger.LogInformation("Generating embeddings for {Count} texts", texts.Count);

            var request = new EmbeddingRequest
            {
                Input = texts,
                Model = _model
            };

            var response = await _client.CreateEmbeddingAsync(request);

            return response.Data
                .OrderBy(e => e.Index)
                .Select(e => e.Embedding.ToArray())
                .ToList();
        }
    }
}

  • أولاً -
  • هذا هو عليه.

**لا إعداد GPU، لا تنزيلات نموذج (12GB ملفات)، لا إدارة VRAM.**التنفيذ

  • الخدمات الأساسية الخدمات الأساسية
  • ١ -

خدمة تقديم خدمات

using Anthropic.SDK;
using Anthropic.SDK.Messaging;

namespace BlogRAG.Services
{
    public interface ILLMService
    {
        Task<string> GenerateCompletionAsync(
            string systemPrompt,
            string userPrompt,
            float temperature = 0.7f);

        IAsyncEnumerable<string> GenerateStreamingCompletionAsync(
            string systemPrompt,
            string userPrompt,
            float temperature = 0.7f);
    }

    public class ClaudeLLMService : ILLMService
    {
        private readonly AnthropicClient _client;
        private readonly string _model;
        private readonly ILogger<ClaudeLLMService> _logger;

        public ClaudeLLMService(
            string apiKey,
            string model,
            ILogger<ClaudeLLMService> logger)
        {
            _client = new AnthropicClient(new APIAuthentication(apiKey));
            _model = model;
            _logger = logger;
        }

        public async Task<string> GenerateCompletionAsync(
            string systemPrompt,
            string userPrompt,
            float temperature = 0.7f)
        {
            _logger.LogInformation("Generating completion with temperature {Temp}", temperature);

            var messages = new List<Message>
            {
                new Message
                {
                    Role = RoleType.User,
                    Content = userPrompt
                }
            };

            var request = new MessageRequest
            {
                Model = _model,
                MaxTokens = 2048,
                Temperature = temperature,
                System = systemPrompt,
                Messages = messages
            };

            var response = await _client.Messages.CreateAsync(request);

            return response.Content.First().Text;
        }

        public async IAsyncEnumerable<string> GenerateStreamingCompletionAsync(
            string systemPrompt,
            string userPrompt,
            float temperature = 0.7f)
        {
            var messages = new List<Message>
            {
                new Message { Role = RoleType.User, Content = userPrompt }
            };

            var request = new MessageRequest
            {
                Model = _model,
                MaxTokens = 2048,
                Temperature = temperature,
                System = systemPrompt,
                Messages = messages,
                Stream = true
            };

            await foreach (var chunk in _client.Messages.StreamAsync(request))
            {
                if (chunk.Delta?.Text != null)
                {
                    yield return chunk.Delta.Text;
                }
            }
        }
    }
}

الفوائد الرئيسية مقابل المحلية:

  • لا OnnNX تنفيذ التنفيذ وقت الإعداد
  • إدارة ذاكرة لا
  • (أ) الدفعات المنفذة بصورة تلقائية بواسطة OOAI
  • ثانياً - نوعية التزكية

****:

  • : = 0.0001 دولار للعربات 1 كلك (رخيصة جداً)
  • تجهيز 100 مدوّنة (بعربات 500 ألف دولار): حوالي 0.05 دولار
  • الاستعمال اليومي (الاستفسارات): نحو 0.001 دولار في اليوم = 0.30 دولار في الشهر

)٢( A.

خدمات خدمات

using Qdrant.Client;
using Qdrant.Client.Grpc;

namespace BlogRAG.Services
{
    public class QdrantVectorStore
    {
        private readonly QdrantClient _client;
        private readonly string _collectionName;
        private readonly ILogger<QdrantVectorStore> _logger;

        public QdrantVectorStore(
            string url,
            string apiKey,
            string collectionName,
            ILogger<QdrantVectorStore> logger)
        {
            _client = new QdrantClient(url, apiKey: apiKey);
            _collectionName = collectionName;
            _logger = logger;
        }

        public async Task CreateCollectionAsync(int vectorSize)
        {
            var collections = await _client.ListCollectionsAsync();

            if (collections.Any(c => c.Name == _collectionName))
            {
                _logger.LogInformation("Collection {Name} already exists", _collectionName);
                return;
            }

            await _client.CreateCollectionAsync(
                collectionName: _collectionName,
                vectorsConfig: new VectorParams
                {
                    Size = (ulong)vectorSize,
                    Distance = Distance.Cosine
                });

            _logger.LogInformation("Created collection {Name}", _collectionName);
        }

        public async Task UpsertAsync(
            Guid id,
            float[] vector,
            Dictionary<string, object> payload)
        {
            var point = new PointStruct
            {
                Id = id,
                Vectors = vector,
                Payload = payload
            };

            await _client.UpsertAsync(_collectionName, new[] { point });
        }

        public async Task<List<ScoredPoint>> SearchAsync(
            float[] queryVector,
            int limit = 10,
            float scoreThreshold = 0.7f)
        {
            var results = await _client.SearchAsync(
                collectionName: _collectionName,
                vector: queryVector,
                limit: (ulong)limit,
                scoreThreshold: scoreThreshold);

            return results.ToList();
        }
    }
}

**الفوائد على المستوى المحلي:**لا تنفيذ نمط التحميل (Inststststststststst

لا حدود قصوى للضخام (يستخدم سياق 200 كاف إذا لزمت الحاجة)

namespace BlogRAG.Services
{
    public class IngestionService
    {
        private readonly IEmbeddingService _embedder;
        private readonly QdrantVectorStore _vectorStore;
        private readonly ILogger<IngestionService> _logger;

        public IngestionService(
            IEmbeddingService embedder,
            QdrantVectorStore vectorStore,
            ILogger<IngestionService> logger)
        {
            _embedder = embedder;
            _vectorStore = vectorStore;
            _logger = logger;
        }

        public async Task IngestMarkdownFilesAsync(string markdownPath)
        {
            var files = Directory.GetFiles(markdownPath, "*.md", SearchOption.AllDirectories);
            _logger.LogInformation("Found {Count} markdown files", files.Length);

            foreach (var file in files)
            {
                await IngestFileAsync(file);
            }
        }

        private async Task IngestFileAsync(string filePath)
        {
            var content = await File.ReadAllTextAsync(filePath);
            var metadata = ExtractMetadata(content);
            var chunks = ChunkContent(content);

            _logger.LogInformation("Processing {File}: {ChunkCount} chunks",
                Path.GetFileName(filePath), chunks.Count);

            // Batch embedding generation
            var texts = chunks.Select(c => c.Text).ToList();
            var embeddings = await _embedder.GenerateBatchEmbeddingsAsync(texts);

            // Upload to Qdrant
            for (int i = 0; i < chunks.Count; i++)
            {
                var chunk = chunks[i];
                var embedding = embeddings[i];

                var payload = new Dictionary<string, object>
                {
                    ["text"] = chunk.Text,
                    ["file_path"] = filePath,
                    ["blog_post_slug"] = metadata.Slug,
                    ["blog_post_title"] = metadata.Title,
                    ["chunk_index"] = i,
                    ["category"] = metadata.Category
                };

                await _vectorStore.UpsertAsync(Guid.NewGuid(), embedding, payload);
            }

            _logger.LogInformation("Ingested {File}", Path.GetFileName(filePath));
        }

        private List<TextChunk> ChunkContent(string content, int chunkSize = 500, int overlap = 50)
        {
            // Simple sentence-aware chunking
            var sentences = content.Split(new[] { ". ", ".\n", "!\n", "?\n" },
                StringSplitOptions.RemoveEmptyEntries);

            var chunks = new List<TextChunk>();
            var currentChunk = new StringBuilder();
            var currentLength = 0;

            foreach (var sentence in sentences)
            {
                if (currentLength + sentence.Length > chunkSize && currentChunk.Length > 0)
                {
                    chunks.Add(new TextChunk { Text = currentChunk.ToString() });

                    // Overlap: keep last sentence
                    currentChunk.Clear();
                    currentLength = 0;
                }

                currentChunk.Append(sentence).Append(". ");
                currentLength += sentence.Length;
            }

            if (currentChunk.Length > 0)
            {
                chunks.Add(new TextChunk { Text = currentChunk.ToString() });
            }

            return chunks;
        }

        private BlogMetadata ExtractMetadata(string content)
        {
            // Extract from markdown frontmatter or HTML comments
            var titleMatch = Regex.Match(content, @"^#\s+(.+)$", RegexOptions.Multiline);
            var categoryMatch = Regex.Match(content, @"");

            return new BlogMetadata
            {
                Title = titleMatch.Success ? titleMatch.Groups[1].Value : "Untitled",
                Category = categoryMatch.Success ? categoryMatch.Groups[1].Value : "General",
                Slug = Path.GetFileNameWithoutExtension(content)
            };
        }
    }

    public class TextChunk
    {
        public string Text { get; set; } = string.Empty;
    }

    public class BlogMetadata
    {
        public string Title { get; set; } = string.Empty;
        public string Category { get; set; } = string.Empty;
        public string Slug { get; set; } = string.Empty;
    }
}

جودة إنتاج أفضل (على الأقل نظرياً - ما زلت أختبر)

namespace BlogRAG.Services
{
    public class RAGGenerationService
    {
        private readonly IEmbeddingService _embedder;
        private readonly QdrantVectorStore _vectorStore;
        private readonly ILLMService _llm;
        private readonly ILogger<RAGGenerationService> _logger;

        public RAGGenerationService(
            IEmbeddingService embedder,
            QdrantVectorStore vectorStore,
            ILLMService llm,
            ILogger<RAGGenerationService> logger)
        {
            _embedder = embedder;
            _vectorStore = vectorStore;
            _llm = llm;
            _logger = logger;
        }

        public async Task<string> GenerateSuggestionAsync(
            string currentDraft,
            string requestType = "continue")
        {
            // 1. Generate embedding for current draft
            var draftEmbedding = await _embedder.GenerateEmbeddingAsync(currentDraft);

            // 2. Search for relevant past content
            var results = await _vectorStore.SearchAsync(
                queryVector: draftEmbedding,
                limit: 5,
                scoreThreshold: 0.7f);

            _logger.LogInformation("Found {Count} relevant chunks", results.Count);

            // 3. Build context from results
            var contextBuilder = new StringBuilder();
            foreach (var result in results)
            {
                var text = result.Payload["text"].ToString();
                var title = result.Payload["blog_post_title"].ToString();
                var score = result.Score;

                contextBuilder.AppendLine($"## From: {title} (relevance: {score:F2})");
                contextBuilder.AppendLine(text);
                contextBuilder.AppendLine();
            }

            // 4. Build prompt
            var systemPrompt = BuildSystemPrompt(requestType);
            var userPrompt = BuildUserPrompt(currentDraft, contextBuilder.ToString(), requestType);

            // 5. Generate with LLM
            var suggestion = await _llm.GenerateCompletionAsync(
                systemPrompt: systemPrompt,
                userPrompt: userPrompt,
                temperature: 0.7f);

            return suggestion;
        }

        private string BuildSystemPrompt(string requestType)
        {
            return requestType switch
            {
                "continue" => @"You are a technical blog writing assistant. Your role is to suggest
                    continuations for blog posts based on the author's past writing style and content.

                    Guidelines:
                    - Match the author's voice and technical depth
                    - Use similar patterns and structures from past posts
                    - Be specific and technical, not generic
                    - Include code examples when relevant
                    - Maintain consistency with past content",

                "improve" => @"You are a technical blog editor. Your role is to improve sections
                    of blog posts while maintaining the author's voice.

                    Guidelines:
                    - Preserve the author's style
                    - Improve clarity and flow
                    - Add technical depth where appropriate
                    - Suggest better examples from past posts
                    - Fix unclear explanations",

                "outline" => @"You are a technical blog outline generator. Your role is to suggest
                    outlines for new blog posts based on past structures.

                    Guidelines:
                    - Study the author's typical post structure
                    - Suggest sections based on successful past posts
                    - Include technical depth appropriate to topic
                    - Reference similar past articles",

                _ => "You are a helpful technical writing assistant."
            };
        }

        private string BuildUserPrompt(string currentDraft, string context, string requestType)
        {
            return $@"
# Current Draft
{currentDraft}

# Relevant Past Content
{context}

# Request
{GetRequestDescription(requestType)}

Please provide your suggestion based on the current draft and the relevant past content shown above.
Remember to maintain consistency with the author's past writing style and technical approach.
";
        }

        private string GetRequestDescription(string requestType)
        {
            return requestType switch
            {
                "continue" => "Continue writing from where the draft ends. Suggest the next 1-2 paragraphs.",
                "improve" => "Improve the current draft. Suggest specific edits and enhancements.",
                "outline" => "Create a detailed outline for completing this post.",
                _ => "Provide helpful suggestions."
            };
        }
    }
}

الطور يعمل بشكل كامل

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace BlogRAG.Console
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // Setup DI and configuration
            var services = new ServiceCollection();

            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .AddUserSecrets<Program>()  // For API keys
                .Build();

            services.AddLogging(builder => builder.AddConsole());

            // Register services
            var embeddingConfig = configuration.GetSection("BlogRAG:Embedding");
            services.AddSingleton<IEmbeddingService>(sp =>
                new OpenAIEmbeddingService(
                    embeddingConfig["ApiKey"]!,
                    embeddingConfig["Model"]!,
                    sp.GetRequiredService<ILogger<OpenAIEmbeddingService>>()));

            var llmConfig = configuration.GetSection("BlogRAG:LLM");
            services.AddSingleton<ILLMService>(sp =>
                new ClaudeLLMService(
                    llmConfig["ApiKey"]!,
                    llmConfig["Model"]!,
                    sp.GetRequiredService<ILogger<ClaudeLLMService>>()));

            var vectorConfig = configuration.GetSection("BlogRAG:VectorStore");
            services.AddSingleton(sp =>
                new QdrantVectorStore(
                    vectorConfig["Url"]!,
                    vectorConfig["ApiKey"] ?? "",
                    vectorConfig["CollectionName"]!,
                    sp.GetRequiredService<ILogger<QdrantVectorStore>>()));

            services.AddSingleton<IngestionService>();
            services.AddSingleton<RAGGenerationService>();

            var serviceProvider = services.BuildServiceProvider();

            // Run CLI
            await RunCLI(serviceProvider, configuration);
        }

        static async Task RunCLI(ServiceProvider serviceProvider, IConfiguration configuration)
        {
            System.Console.WriteLine("=== Blog RAG Assistant ===\n");
            System.Console.WriteLine("Commands:");
            System.Console.WriteLine("  ingest - Ingest markdown files");
            System.Console.WriteLine("  write - Start writing session");
            System.Console.WriteLine("  quit - Exit\n");

            while (true)
            {
                System.Console.Write("> ");
                var command = System.Console.ReadLine()?.Trim().ToLower();

                switch (command)
                {
                    case "ingest":
                        await IngestCommand(serviceProvider, configuration);
                        break;
                    case "write":
                        await WriteCommand(serviceProvider);
                        break;
                    case "quit":
                        return;
                    default:
                        System.Console.WriteLine("Unknown command");
                        break;
                }
            }
        }

        static async Task IngestCommand(ServiceProvider serviceProvider, IConfiguration configuration)
        {
            var ingestion = serviceProvider.GetRequiredService<IngestionService>();
            var markdownPath = configuration["BlogRAG:Ingestion:MarkdownPath"];

            System.Console.WriteLine($"Ingesting from {markdownPath}...");
            await ingestion.IngestMarkdownFilesAsync(markdownPath!);
            System.Console.WriteLine("Ingestion complete!\n");
        }

        static async Task WriteCommand(ServiceProvider serviceProvider)
        {
            var rag = serviceProvider.GetRequiredService<RAGGenerationService>();

            System.Console.WriteLine("\nEnter your draft (end with empty line):");
            var draft = new StringBuilder();
            string? line;

            while (!string.IsNullOrWhiteSpace(line = System.Console.ReadLine()))
            {
                draft.AppendLine(line);
            }

            System.Console.WriteLine("\nGenerating suggestion...\n");
            var suggestion = await rag.GenerateSuggestionAsync(draft.ToString());

            System.Console.WriteLine("=== Suggestion ===");
            System.Console.WriteLine(suggestion);
            System.Console.WriteLine("\n");
        }
    }
}

3,5 كل شيء 3,5 سونت: 3 دولار/بملايين الدولارات رمزات المدخلات، 15 مليون دولار الناتج

# 1. Clone/create project
dotnet new console -n BlogRAG
cd BlogRAG

# 2. Add packages
dotnet add package Qdrant.Client
dotnet add package OpenAI
dotnet add package Anthropic.SDK
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.UserSecrets

# 3. Set API keys (stored securely)
dotnet user-secrets init
dotnet user-secrets set "BlogRAG:Embedding:ApiKey" "sk-..."
dotnet user-secrets set "BlogRAG:LLM:ApiKey" "sk-ant-..."

# 4. Start Qdrant (local)
docker run -d -p 6333:6333 qdrant/qdrant

# 5. Run ingestion
dotnet run
> ingest

# 6. Start writing
> write

**جلسة لكتابة المدوّنة النموذجية (20 مدخل ك ك، ناتج 2 ك: زهاء 0.09 دولار)**الاستخدام الشهري (10 دورات): حوالي 0.90 دولار في الشهر

هذه هي أرقام الملعب استناداً إلى تجاربي المبكرة - ميلك قد يختلف تبعاً لمدى ثرثرتك مع AI.

# Start Qdrant (if using local Docker)
docker start qdrant

# Run assistant
dotnet run
> write

# Enter your draft
I've been working on a new feature that uses Entity Framework Core...
[Ctrl+D or empty line]

# Get AI suggestion based on your past EF posts!

أولاً -

مُتَزَوَج مُتَجَزَرَاتِ مُقَرَّر ( كَمَا كَمَا كَانَتْ مُسْتَرِكَةً ! )

& مُث_ مُ_ محلي-فقط اشير إلى (دوكر) المحلي أو (كدرانت كلاود )!

تنفيذ خط خطاط |-----------|--------|------| الخدمة المولدة مُز تشغيل النظام | إعداد إعداد إعداد الوقت الأول | | ~$3.65 |

مجموع وقت الإعداد

  • تقريباً 15 دقيقة مقابل ساعتين تقريباً للإعداد المحلي GPU - على افتراض أن كل شيء يسير بسلاسة،

**تقديرات التكلفة الشهرية (مدرج شخصي)**سيناريو

كتابة 4 مدوّنات شهرياً

  1. □ عملية الأمم المتحدة في كوت ديفوار المجلد الأول/ المجلد الأول:

    • text-embedding-3-small(100 وظيفة) - لمرة واحدة، 500 ألف رمز رمزي
    • text-embedding-3-large□ رسومات (استبيانات، 40 في الشهر) 40 ألف رسم رمزي (40 في الشهر)
    • (40 مقترحاً) - إدخال 800 كلف، ناتج 80 كلف 3.60 دولار
  2. **)أ(**لأغراض المقارنة:

    // Use OpenAI Batch API for ingestion
    var batch = await client.CreateBatchAsync(requests);
    // Wait hours, pay half price
    
  3. الإعداد المحلي: دولار واحد في الشهر (ولكن 800 دولار + GPU مقدماً):

    // Don't re-embed identical text
    var cache = new Dictionary<string, float[]>();
    
  4. ITAGPPT: 20 دولاراً شهرياً (لا RAG، عامة):

    • التريم: 12 دولاراً في الشهر (لا توجد كتابة بالبلاغات المستقلة)
    • نقطة فاصل نقطة
  5. إذا كنت تستخدم هذا لمدة 18 أشهر + أشهر، GPU المحلي يدفع لنفسه.:

    // Retrieve top 3 instead of top 10 chunks
    limit: 3  // 70% less input tokens
    

وإلا فإن السحب أرخص.

على الرغم من أنني لا زلت أعمل على ما إذا كانت توقعات التكلفة دقيقة ربما سأتناول كلماتي بعد بضعة أشهر عندما تأتي الفواتير

التكاليف الفعلية: |-------|---------|---------|--------|--------| & 000 002 1 كُلُفْ من الرسوم البيانية: 000 013 1/1 كُلُفُكُرْ التعادل في التكلفة!

مُنافِض المُنِيات(50 في المائة أرخص لغير الراضي):

ملصقات محليّ محليّ محليّ

// Switch models with one line
services.AddSingleton<ILLMService>(sp =>
    new ClaudeLLMService(  // Was GPT-4, now Claude
        config["ApiKey"],
        "claude-3-5-sonnet-20241022",  // Latest model
        sp.GetRequiredService<ILogger<ClaudeLLMService>>()));

استخدام نماذج أرخص للمسودات3.5 كلف كلود Haiko: 0.25 دولار/مدخل (12x أرخص من سونيت)

GPPT-4o-mini: 0.15 دولار/مدخل (أرخص من GPT-4)

# Works on Mac (no CUDA support)
dotnet run  # Just works!

# Works on Linux ARM (Raspberry Pi?)
dotnet run  # Just works!

# Works in Codespaces/Gitpod
dotnet run  # Just works!

نافذة الإطار

محلي تثبيت

// Handle 100 concurrent users? Easy with APIs
await Task.WhenAll(users.Select(u =>
    rag.GenerateSuggestionAsync(u.Draft)));

// Local? Limited by your single GPU

١ -

bash

Deploy to Azure/AWS/GCP

dotnet publish -c Release

Upload single binary, set env vars, done

Local? Need to:

- Include 12GB model files

- Install CUDA on target machine

- Ensure GPU drivers

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.