# Bygga en "Lawyer GPT" för din blogg - Fine Tuning LLM Alternativ: RAG med Qdrant och Generisk LLMs

<!--category-- AI, LLM, RAG, C#, Cloud, Qdrant, OpenAI -->
<datetime class="hidden">1973-02-08T1:59</datetime>

## Inledning

I min[8-delars serie "Lagstiftare GPT"](/blog/building-a-lawyer-gpt-for-your-blog-part1), Jag visade dig hur man bygger en komplett lokal RAG-baserad skrivassistent med GPU acceleration, lokala LLMs, och vektordatabaser.

Det är kraftfullt, privat, och körs helt på din hårdvara.

Men låt oss vara ärliga - inte alla har en arbetsstation med en NVIDIA GPU, 96GB RAM, och tålamod att sätta upp CUDA, cuDNN, och wrangle GGUF modeller.

Tänk om du bara vill ha fördelarna med en blogg skrivande assistent utan hårdvara investering?

> Denna artikel presenterar det molnbaserade alternativet: samma RAG metod, samma Qdrant vektor databas, men med moln LLM API istället för lokala slutsatser.

## Se det som "Lawyer GPT Lite" - enklare installation, lägre hinder för inträde, och potentiellt bättre utskriftskvalitet med hjälp av gränsmodeller.

### Fullständigt avslöjande: Jag håller fortfarande på att lära mig vilket tillvägagångssätt som fungerar bäst i praktiken, så ta mina kostnadsberäkningar och prestationsanspråk med en nypa salt.

Vad jag kan säga är att detta moln tillvägagångssätt har visat sig anmärkningsvärt enkelt att ställa upp jämfört med GPU-rutten.

- OBS: Detta är en del av mina experiment med AI (assisterad utformning) + min egen redigering.
- Samma röst, samma pragmatism, bara snabbare fingrar.
- Varför ett molnalternativ?
- Den ursprungliga metoden
- Hela serien "Lawyer GPT" bygger upp ett system som:
- Körs 100% lokalt (privacy)
- Inga API-kostnader

### Snabb GPU-accelererad slutsats

Kräver NVIDIA GPU (8GB+ VRAM)

- Komplex installation (CUDA, cuDNN, modellhantering)
- Begränsat till modeller som passar i VRAM
- Windows-fokuserad distribution
- Molnalternativet
- Detta tillvägagångssätt ger dig:
- Ingen GPU krävs (körningar på någon maskin)
- Enkel inställning (ingen CUDA/cuDNN)
- Tillgång till gränsmodeller (GPT-4, Claude, etc.)

**Plattformsoberoende (Windows, Mac, Linux)**

Bättre produktionskvalitet (större, mer kapabla modeller)
|--------------------|-------------------|
API-kostnader (även om de är rimliga för personligt bruk)
Data som skickas till tredje parts API:er
Latency beror på nätverket
När ska jag använda vilken?

[TOC]

## Använd lokal metod  och använd molnmetoden  på

Prövning i privatlivet är av avgörande betydelse för bekvämligheten

```mermaid
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
```

**Om du har GPU-hårdvara så är du på Mac/Linux/laptop**

- **på hög volym användning  på moderat användning (få inlägg/månad)**Du tycker om att mixtra Du vill ha resultat snabbt`text-embedding-3-small`Översikt över arkitekturen
- **Molnet versionen håller samma RAG grundprinciper men byter lokala LLM inference för API samtal:**Viktiga skillnader:
- **Inbäddningsmodell**: OpenAI:s
- **API istället för lokal BGE-modell**LLM Ordförande

Claude 3.5 Sonnet eller GPT-4 API istället för lokala Mistral/Llama

## Ingen GPU krävs

### : Allt CPU-baserade lokalt, beräkna händer i moln

- **Enklare utplacering**: Enkel körbar, inga modellfiler att hantera
- **Jag bör notera att jag inte har kört omfattande riktmärken som jämför de två tillvägagångssätten ännu - jag är fortfarande i den undersökande fasen själv.**Men de första resultaten är lovande nog att dela med sig.

### Teknologi Stack

- **Kärnramar**.NET 9 Ordförande
- **- Samma som originalserien**Detta beslut träder i kraft den tjugonde dagen efter det att det har offentliggjorts i Europeiska unionens officiella tidning.
- **- Moderna språkfunktioner**Moln API: er

### OpenAI API

- **- Inbäddningar (text-inbäddning-3-små) + LLM (GPT-4)**Antropiskt API
- **- Alternativ LLM (Claude 3.5 Sonnet)**Båda

### - Du kan mixa och matcha!

- **Vektordatabas**Qdrant Ordförande
- **- Samma som original, kan köra lokalt via Docker eller använda Qdrant Cloud**Alternativt
- **: Pinecone, Vävnadsmoln (hanterade alternativ)**Klientalternativ

## Konsolapp

### - Simplest, bra för tester

**Blazor WebAssembly Ordförande**

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

**- Webbbaserad, fungerar var som helst**

1. Vegetabiliska fetter och oljor samt fraktioner av sådana fetter eller oljor, även raffinerade men inte kemiskt modifierade[- Plattformsoberoende skrivbord (Windows, Mac, Linux)](https://cloud.qdrant.io)
2. Inställning: Snabbsökvägen
3. 1. Vad är det som händer?

**Installera Qdrant**

### Alternativ A: Lokal docka (Rekommenderas för utveckling)

**Alternativ B: Qdrantmoln (lättaste)**Anmäl dig på

1. cloud.qdrant.io[Skapa ett gratis kluster](https://platform.openai.com)
2. Hämta din API-nyckel och klusterwebbadress
3. Ingen CUDA, ingen cudNN, inga förarinstallationer behövs!

**Två.**Hämta API- nycklar

1. OpenAI[(för sängkläder + LLM):](https://console.anthropic.com)
2. Gå till

### platform.openai.com

Skapa API- nyckel`appsettings.json`:

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

**Ange användningsgränser (viktigt!)**Antropiskt

## (frivillig uppgift för Claude):

### Gå till

#### console.anthropic.com

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

**Skapa API- nyckel**

- 3. Vad är det som händer?
- Inställning
- Skapa
- Så där ja.

**Ingen GPU-inställning, inga modellnedladdningar (12GB-filer), ingen VRAM-hantering.**Genomförande

- Kärntjänster
- 1. Vad är det som händer?

#### Tjänsten för inbäddning av moln

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

**Viktiga fördelar jämfört med lokala:**

- Ingen inställning av ONNX körtid
- Ingen GPU-minneshantering
- Automatisk batchning av OpenAI
- Toppmoderna inredningskvaliteter

**Kostnad**:

- : ~$0.0001 per 1K tokens (mycket billigt)
- Bearbetning 100 blogginlägg (~500K tokens): ~$0.05
- Daglig användning (10 frågor): ~$0.001/dag = $0.30/månad

Två.

#### LLM- molntjänst

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

**Fördelar jämfört med lokala:**Ingen modellbelastning (snabbstart)

### Inga VRAM-gränser (använd 200K sammanhang om det behövs)

```csharp
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, @"<!--category--\s+(.+)-->");

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

### Bättre produktionskvalitet (åtminstone i teorin - jag testar fortfarande)

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

## Streaming fungerar perfekt

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

## Kostnad

### Claude 3.5 Sonnet: $3 / miljoner inmatningspolletter, $15 / miljoner utdata

```bash
# 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
```

**Typisk bloggskrivarsession (20K-inmatning, 2K-utmatning): ungefär $0.09**Månatlig användning (10 sessioner): ungefär $0.90/månad

### Detta är ballpark siffror baserat på mina tidiga experiment - din körsträcka kan variera beroende på hur pratsam du är med AI.

```bash
# 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. Vad är det som händer?

### Qdrant Vector Store (Samma som original!)

**Samma API som lokal inställning**- bara peka på lokala Docker eller Qdrant Cloud!

Intag av pipeline
|-----------|--------|------|
RAG- genereringstjänst
Enkel konsolklientName
Köra systemet
| **Första gången du ställer in** | | **~$3.65** |

**Total installationstid**

- : Ca 15 minuter vs ca 2 timmar för lokal GPU installation - förutsatt att allt går smidigt, vilket enligt min erfarenhet är ett farligt antagande att göra.
- Daglig användning
- Kostnadsanalys

**Månadskostnadsberäkning (Personlig blogg)**Scenario

### : Skriva 4 blogginlägg per månad

1. **på drift  på volym  på kostnad  på**:
   
   - `text-embedding-3-small`Initial intag (100 inlägg)  på en gång, 500K polletter  på $0.05
   - `text-embedding-3-large`Inbäddningar (queries, 40/månad)  på 40K polletter på $0.004
   - på LLM samtal (40 förslag)  på 800K input, 80K utdata  på $3,60

2. **Totalt per månad**Som jämförelse kan nämnas följande:
   
   ```csharp
   // Use OpenAI Batch API for ingestion
   var batch = await client.CreateBatchAsync(requests);
   // Wait hours, pay half price
   ```

3. **Lokal inställning: $0/månad (men $ 800 + GPU i förväg)**:
   
   ```csharp
   // Don't re-embed identical text
   var cache = new Dictionary<string, float[]>();
   ```

4. **ChatgPT Plus: $20/månad (ingen RAG, generisk)**:
   
   - Grammarly Premium: $12/månad (ingen AI skrivande)
   - Bryt-jämn punkt

5. **: Om du skulle använda detta för 18 + månader, lokala GPU betalar för sig själv.**:
   
   ```csharp
   // Retrieve top 3 instead of top 10 chunks
   limit: 3  // 70% less input tokens
   ```

## Annars är molnet billigare.

### Även om jag fortfarande funderar på om mina kostnadsberäkningar är korrekta - kan jag äta mina ord om några månader när räkningarna kommer in.

Kostnadsoptimering tips
|-------|---------|---------|--------|--------|
Använd mindre modeller för inbäddning
: $0.00002/1K tokens
: $0.00013/1K tokens
6,5x kostnadsskillnad!

**Batch API-samtal**(50 % billigare för icke-brådskande):

### Cache inbäddar lokalt

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

**Använd billigare modeller för utkast**Claude 3.5 Haiku: $0.25/M ingång (12x billigare än Sonnet)

### GPT-4o-mini: $0.15/M ingång (20x billigare än GPT-4)

```bash
# 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!
```

**Begränsa sammanhangsfönster**

### Fördelar med lokal inställning

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

// Local? Limited by your single GPU
```

### 1. Vad är det som händer?

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
