Construire un "avocat GPT" pour votre blog - Fine Tuning LLM Alternative: RAG avec Qdrant et générique en ligne LLM (Français (French))

Construire un "avocat GPT" pour votre blog - Fine Tuning LLM Alternative: RAG avec Qdrant et générique en ligne LLM

Wednesday, 12 November 2025

//

27 minute read

Présentation

Dans monSérie 8 « Avocat GPT », je vous ai montré comment construire un assistant d'écriture local complet basé sur RAG en utilisant l'accélération GPU, les LLM locaux et les bases de données vectorielles.

Il est puissant, privé, et fonctionne entièrement sur votre matériel.

Mais soyons honnêtes - tout le monde n'a pas un poste de travail avec un GPU NVIDIA, 96 Go de RAM, et la patience de mettre en place des modèles CUDA, cudNN et wrangle GGUF.

Que faire si vous voulez juste les avantages d'un assistant d'écriture de blog sans l'investissement matériel?

Cet article présente l'alternative basée sur le cloud : la même approche RAG, la même base de données vectoriels Qdrant, mais en utilisant les API LLM du cloud plutôt que l'inférence locale.

Pensez-y comme « Lawyer GPT Lite » - installation plus facile, barrière inférieure à l'entrée, et potentiellement une meilleure qualité de sortie à l'aide de modèles frontières.

Divulgation complète: J'apprends toujours quelle approche fonctionne le mieux dans la pratique, alors prenez mes estimations de coûts et les demandes de rendement avec une pincée de sel.

Ce que je peux dire, c'est que cette approche cloud s'est révélée remarquablement simple à mettre en place par rapport à la route GPU.

  • REMARQUE: Ceci fait partie de mes expériences avec l'IA (couture assistée) + mon propre montage.
  • Même voix, même pragmatisme, juste des doigts plus rapides.
  • Pourquoi une alternative nuageuse ?
  • L'approche initiale
  • La série complète « Lawyer GPT » construit un système qui :
  • Exécute 100% localement (privacité)
  • Aucun coût de l'API

Inférence accélérée rapide du GPU

Nécessite un GPU NVIDIA (8GB+ VRAM)

  • Configuration complexe (CUDA, cudNN, gestion de modèles)
  • Limité aux modèles que vous pouvez intégrer dans VRAM
  • Déploiement axé sur Windows
  • L'Alternative Nuage
  • Cette approche vous donne :
  • Pas de GPU requis (courses sur n'importe quelle machine)
  • Configuration simple (pas de CUDA/cuDNN)
  • Accès aux modèles frontières (GPT-4, Claude, etc.)

Plateforme croisée (Windows, Mac, Linux)

Une meilleure qualité de sortie (modèles plus grands et plus performants) |--------------------|-------------------| Coûts de l'API (bien que raisonnables pour une utilisation personnelle) Données envoyées à des API tierces La latence dépend du réseau Quand utiliser lequel ?

Utiliser l'approche locale Utiliser l'approche en nuage

La vie privée est critique

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

Vous avez du matériel GPU. Vous êtes sur Mac/Linux/Laptop.

  • **Utilité élevée Utilité modérée (peu de postes/mois)**Vous aimez le bricolage Vous voulez des résultats rapidementtext-embedding-3-smallVue d'ensemble de l'architecture
  • **La version cloud conserve les mêmes fondamentaux RAG mais échange l'inférence locale LLM pour les appels API :**Principales différences:
  • Modèle d'intégration: OpenAI's
  • API au lieu du modèle BGE localLLM

: Claude 3.5 Sonnet ou API GPT-4 au lieu de Mistral/Llama local

Pas de GPU requis

: Tout ce qui est basé sur CPU localement, le calcul se produit dans le cloud

  • Déploiement plus simple: exécutable unique, aucun fichier modèle à gérer
  • **Je dois noter que je n'ai pas encore effectué d'analyses comparatives exhaustives des deux approches - je suis encore dans la phase exploratoire moi-même.**Mais les premiers résultats sont assez prometteurs pour être partagés.

Stack technologique

  • Cadre de base.NET 9
  • - Même série que d'origineC# 13
  • - Caractéristiques linguistiques modernesAPI en nuage

API OpenAI

  • **- Embeddings (texte-embuding-3-small) + LLM (GPT-4)**API anthropique
  • **- Alternative LLM (Claude 3.5 Sonnet)**Les deux

- Tu peux te mélanger et s'allumer !

  • Base de données vectoriellesQdurant
  • - Même que d'origine, peut fonctionner localement via Docker ou utiliser Qdrant CloudVariante
  • **: Pinecone, Weaviate Cloud (options gérées)**Options client

Console App

- Plus simple, parfait pour les tests

Assemblée Web Blazor

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

- Web-basé, travaille n'importe où

  1. Avalonie- Bureau multiplateforme (Windows, Mac, Linux)
  2. Configuration : le chemin rapide
    1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

Installer Qdurant

Option A : Docker local (Recommandé pour le développement)

**Option B: Nuage Qdrant (plus facile)**Inscrivez-vous à

  1. Nuage.qdurant.ioCréer un cluster libre
  2. Obtenez votre API clé et l'URL du cluster
  3. Pas de CUDA, pas de cudNN, pas d'installations de conduite nécessaires !

**2. Le Président. — L'ordre du jour appelle le rapport (doc.**Obtenir les clés de l'API

  1. Ouverture de l'AI(pour les emboîtements + LLM):
  2. Allez-y.

plate-forme.openai.com

Créer la clé APIappsettings.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
    }
  }
}

**Définir les limites d'utilisation (important!)**Anthropique

(facultatif, pour Claude):

Allez-y.

console.anthropic.com

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();
        }
    }
}

Créer la clé API

    1. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.
  • Configuration
  • Créer
  • C'est ça.

**Pas de configuration GPU, pas de téléchargements de modèles (12 Go de fichiers), pas de gestion VRAM.**Mise en œuvre

  • Services de base
    1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

Service d'intégration des nuages

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;
                }
            }
        }
    }
}

Principaux avantages par rapport aux avantages locaux :

  • Pas de configuration ONNX Runtime
  • Pas de gestion de mémoire GPU
  • Lotage automatique par OpenAI
  • Qualité d'intégration à la fine pointe de la technologie

Coût:

  • : ~$0.0001 par jeton 1K (très bon marché)
  • Traitement de 100 billets de blog (~500K jetons): ~$0,05
  • Utilisation quotidienne (10 requêtes) : ~$0.001/jour = $0.30/mois
  1. Le Président. — L'ordre du jour appelle le rapport (doc.

Service Cloud LLM

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();
        }
    }
}

**Avantages par rapport aux avantages locaux :**Pas de chargement du modèle (démarrage immédiat)

Pas de limites VRAM (utiliser le contexte 200K si nécessaire)

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;
    }
}

Une meilleure qualité de sortie (du moins en théorie - je suis encore à l'essai)

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."
            };
        }
    }
}

Le streaming fonctionne parfaitement

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");
        }
    }
}

Coût

Claude 3.5 Sonnet : jetons d'entrée de 3 \(/M\), 15 \(/M\)

# 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

**Session d'écriture de blog typique (20K entrée, sortie 2K): environ 0,09 $**Utilisation mensuelle (10 séances) : environ 0,90 $/mois

Ce sont des figures de base-ball basées sur mes premières expériences - votre kilométrage peut varier selon la façon dont vous êtes bavard avec l'IA.

# 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. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

Magasin de vectorielle Qdrant (même que d'origine!)

Même API que la configuration locale- pointe juste vers Docker ou Qdurant Cloud!

Pipeline d'ingestion |-----------|--------|------| Service de génération de RAG Client Console simple Exécution du système | Configuration de la première fois | | ~$3.65 |

Temps total de configuration

  • : environ 15 minutes vs environ 2 heures pour la configuration GPU locale - en supposant que tout se passe bien, ce qui dans mon expérience est une hypothèse dangereuse à faire.
  • Utilisation quotidienne
  • Analyse des coûts

**Estimation mensuelle des coûts (Blogue personnel)**Scénario

: Écrire 4 billets de blog par mois

  1. Opération Volume Coût:

    • text-embedding-3-smallL'ingestion initiale (100 postes) Exclusivité, jetons 500K Exclusivité 0,05$
    • text-embedding-3-largeEmbeddings (requêtes, 40/mois)
    • Appel LLM (40 suggestions) Entrée 800K, sortie 80K
  2. Total mensuelPour comparaison:

    // Use OpenAI Batch API for ingestion
    var batch = await client.CreateBatchAsync(requests);
    // Wait hours, pay half price
    
  3. Configuration locale : $0/mois (mais $800+ GPU à l'avance):

    // Don't re-embed identical text
    var cache = new Dictionary<string, float[]>();
    
  4. ChatGPT Plus: 20 $/mois (pas de RAG, générique):

    • Prime Grammaire: 12 $/mois (pas d'écriture d'IA)
    • Point d'équilibre
  5. : Si vous l'utilisez pendant plus de 18 mois, GPU local paie pour lui-même.:

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

Sinon, le nuage est moins cher.

Bien que je travaille toujours sur la question de savoir si mes prévisions de coûts sont exactes - je peux manger mes mots dans quelques mois quand les factures entrent.

Conseils pour l'optimisation des coûts |-------|---------|---------|--------|--------| Utiliser des modèles plus petits pour les emboîtements : 0,00002/1K jetons : 0,00013/1K jetons 6,5x différence de coût!

Appels d'API par lots(50% moins cher pour les non-urgents):

Cache embarque localement

// 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>>()));

Utiliser des modèles moins chers pour les ébauchesClaude 3.5 Haiku: 0,25$/M entrée (12x moins cher que Sonnet)

GPT-4o-mini: 0,15 $/M entrée (20x moins cher que 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!

Fenêtre de contexte limite

Avantages sur la configuration locale

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

// Local? Limited by your single GPU

1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

# 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
# - Manage VRAM

Meilleure qualité du modèle

Modèle Contexte Qualité Locale Nuage

Mistral 7B) 8K) Bon (besoin de 8 Go VRAM) Oui (besoin de 8 Go VRAM)

Lama 3 70 B 8 K Excellent (besoin de 48 Go VRAM) Oui

  • GPT-4 Turbo. 128K. Excellent. Non. Oui.
  • Claude 3.5 Sonnet (en anglais) 200K) Meilleur (en anglais) Non (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais) Oui (en anglais)
  • Cloud vous donne accès aux modèles 70B+
  • qui nécessiterait un matériel GPU de 10 K$ et plus.

Du moins, c'est la théorie - j'apprends encore si les modèles plus grands produisent réellement un contenu de blog nettement meilleur dans la pratique.

2. Le Président. — L'ordre du jour appelle le rapport (doc.

Mises à jour instantanées

Aucun téléchargement de modèle

  • , pas de conversion GGUF, pas de contrôle de compatibilité.
  • C'est vraiment génial quand vous expérimentez différents modèles pour voir ce qui fonctionne le mieux.
    1. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

Forme croisée

L'approche locale est Windows + NVIDIA uniquement.

  1. Le Président. — L'ordre du jour appelle le rapport (doc.
  • Échelle
  • Déploiement plus simple

Limitations et compromis

1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

Préoccupations relatives à la protection de la vie privée

// Use abstraction layer
public interface ILLMService
{
    // Switch providers easily
}

// Factory pattern
services.AddSingleton<ILLMService>(sp =>
{
    return config["Provider"] switch
    {
        "OpenAI" => new OpenAILLMService(...),
        "Anthropic" => new ClaudeLLMService(...),
        "Cohere" => new CohereLLMService(...),
        _ => throw new Exception("Unknown provider")
    };
});

Votre contenu de blog va à OpenAI/Anthropic.

**Atténuation:**Utilisation uniquement pour le contenu du blog public

public class HybridEmbeddingService : IEmbeddingService
{
    private readonly LocalOnnxEmbedding _local;
    private readonly OpenAIEmbeddingService _cloud;
    private readonly bool _preferLocal;

    public async Task<float[]> GenerateEmbeddingAsync(string text)
    {
        if (_preferLocal && _local.IsAvailable())
        {
            return _local.GenerateEmbedding(text);  // Free, fast
        }

        return await _cloud.GenerateEmbeddingAsync(text);  // Fallback
    }
}

Vérifier la politique d'utilisation des données du fournisseur:

  • OpenAI: données API non utilisées pour la formation (à partir de 2024)
  • Anthropique : Même engagement

Si vous rédigez un contenu confidentiel, utilisez l'approche locale.**Je suis à l'aise avec cela pour mon blog public, mais je ne l'utiliserais pas pour quelque chose de sensible à distance - et vous ne devriez pas prendre ma parole pour ce que "rémotivement sensible" signifie pour votre cas d'utilisation.**2. Le Président. — L'ordre du jour appelle le rapport (doc.

  1. Dépendance du réseau
  2. Pas d'Internet = pas d'assistant.
  3. Atténuation:

Cache les suggestions précédentes localement

Mettre en œuvre le mode hors ligne pour l'édition

Retour aux modèles locaux plus petits |---------|-------|-------| 3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels. Latence Les appels API prennent 1-3 secondes vs <1s local. Vérification de la réalité : Local: 0.5s génération Nuage: génération 2s Différence : 1,5s (parfaitement acceptable pour écrire de l'aide dans mon expérience - bien que je suppose que cela dépende de votre impatience) 4. Le Président. — L'ordre du jour appelle le rapport (doc.

Verrouillage du fournisseur

  • Le changement d'API nécessite des changements de code.
  • Atténuation:
  • Approche hybride : le meilleur des deux mondes
  • Pouvez-vous mélanger local et nuage ?
  • Absolument !

Utilisez local pour les emboîtements (pas cher, rapide), le cloud pour LLM (questions de qualité)

  • Embeddings locaux : Économisez 0,00$4/mois (faible montant, il est admis)
  • LLM dans le cloud: Obtenez la qualité GPT-4/Claude
  • C'est en fait ma
  • approche recommandée
  • , bien que je continue d'expérimenter pour voir si c'est le bon équilibre:

**Exécuter un petit modèle d'intégration localement (pas de GPU nécessaire)**Utiliser les API en nuage pour LLM

Qdurant local pour le développement, cloud pour la production

  1. **Le présent règlement entre en vigueur le vingtième jour suivant celui de sa publication au Journal officiel de l'Union européenne.**L'alternative nuageuse à "L'avocat GPT" vous donne environ 80% des avantages avec 20% de la complexité - ou du moins cela a été mon expérience jusqu'à présent:
  2. Caractéristiques Locales NuageTemps d'installation: 2-4 heures: 15 minutes:
  3. **Nécessité d'un matériel NVIDIA GPU. N'importe quel ordinateur.**Qualité du modèle: 7B-13B-GPT-4, Claude 3.5.
  4. **Coût mensuel : 0 \(environ 3 à 5\)**Latence: 0,5s 2s
  5. **Protection des renseignements personnels 100% local.Envoyé à l'API.**Contre-plateforme de Fenêtres seulement Mac/Linux/Windows

Maintenance du modèle, mises à jour CUDA Aucun**Quand utiliser le cloud :**Vous n'avez pas NVIDIA GPU

Vous êtes sur Mac/Linux

Vous voulez le chemin le plus facile

Vous écrivez moins de 10 messages/mois

Vous avez du matériel GPU

Bien que je devrais mettre en garde contre le fait que j'apprends encore les gotchas ici.

  • Prochaines étapes[Essaie.

: Configurez la version cloud cet après-midi

Finding related posts...
logo

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